@voxgig/sdkgen 3.5.1 → 3.7.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 (255) hide show
  1. package/bin/voxgig-sdkgen +1 -1
  2. package/dist/cmp/AgentGuideTop.js +17 -0
  3. package/dist/cmp/AgentGuideTop.js.map +1 -1
  4. package/dist/cmp/ExternalTarget.js +1 -1
  5. package/dist/cmp/ExternalTarget.js.map +1 -1
  6. package/dist/cmp/Readme.js +2 -0
  7. package/dist/cmp/Readme.js.map +1 -1
  8. package/dist/cmp/ReadmeStation.d.ts +2 -0
  9. package/dist/cmp/ReadmeStation.js +57 -0
  10. package/dist/cmp/ReadmeStation.js.map +1 -0
  11. package/dist/helpers/naming.d.ts +6 -1
  12. package/dist/helpers/naming.js +156 -5
  13. package/dist/helpers/naming.js.map +1 -1
  14. package/dist/helpers/opShape.js +72 -0
  15. package/dist/helpers/opShape.js.map +1 -1
  16. package/dist/helpers/packageMeta.d.ts +2 -1
  17. package/dist/helpers/packageMeta.js +57 -20
  18. package/dist/helpers/packageMeta.js.map +1 -1
  19. package/dist/sdkgen.d.ts +3 -3
  20. package/dist/sdkgen.js +63 -18
  21. package/dist/sdkgen.js.map +1 -1
  22. package/dist/testkit.d.ts +39 -0
  23. package/dist/testkit.js +663 -0
  24. package/dist/testkit.js.map +1 -0
  25. package/dist/tsconfig.tsbuildinfo +1 -1
  26. package/dist/utility.d.ts +1 -1
  27. package/dist/utility.js +16 -2
  28. package/dist/utility.js.map +1 -1
  29. package/package.json +32 -8
  30. package/project/.sdk/src/cmp/c/Config_c.ts +34 -13
  31. package/project/.sdk/src/cmp/c/Test_c.ts +5 -7
  32. package/project/.sdk/src/cmp/clojure/Test_clojure.ts +3 -7
  33. package/project/.sdk/src/cmp/cpp/Config_cpp.ts +9 -46
  34. package/project/.sdk/src/cmp/cpp/Test_cpp.ts +4 -7
  35. package/project/.sdk/src/cmp/csharp/Config_csharp.ts +7 -1
  36. package/project/.sdk/src/cmp/csharp/Package_csharp.ts +1 -1
  37. package/project/.sdk/src/cmp/csharp/Test_csharp.ts +5 -7
  38. package/project/.sdk/src/cmp/dart/Config_dart.ts +14 -2
  39. package/project/.sdk/src/cmp/dart/Test_dart.ts +3 -4
  40. package/project/.sdk/src/cmp/dart/fragment/Config.data.fragment.dart +5 -0
  41. package/project/.sdk/src/cmp/dart/fragment/Config.fragment.dart +6 -0
  42. package/project/.sdk/src/cmp/dart/fragment/Main.fragment.dart +13 -4
  43. package/project/.sdk/src/cmp/elixir/Config_elixir.ts +12 -2
  44. package/project/.sdk/src/cmp/elixir/Package_elixir.ts +42 -3
  45. package/project/.sdk/src/cmp/elixir/Test_elixir.ts +5 -6
  46. package/project/.sdk/src/cmp/go/Config_go.ts +8 -1
  47. package/project/.sdk/src/cmp/go/EntityTypes_go.ts +1 -1
  48. package/project/.sdk/src/cmp/go/Entity_go.ts +1 -1
  49. package/project/.sdk/src/cmp/go/Gitignore_go.ts +6 -0
  50. package/project/.sdk/src/cmp/go/Main_go.ts +2 -2
  51. package/project/.sdk/src/cmp/go/Package_go.ts +1 -1
  52. package/project/.sdk/src/cmp/go/ReadmeExamplesTest_go.ts +59 -6
  53. package/project/.sdk/src/cmp/go/ReadmeExplanation_go.ts +1 -1
  54. package/project/.sdk/src/cmp/go/ReadmeHowto_go.ts +1 -1
  55. package/project/.sdk/src/cmp/go/ReadmeInstall_go.ts +1 -1
  56. package/project/.sdk/src/cmp/go/ReadmeModel_go.ts +1 -1
  57. package/project/.sdk/src/cmp/go/ReadmeQuick_go.ts +1 -1
  58. package/project/.sdk/src/cmp/go/ReadmeTopQuick_go.ts +1 -1
  59. package/project/.sdk/src/cmp/go/ReadmeTopTest_go.ts +1 -1
  60. package/project/.sdk/src/cmp/go/Test_go.ts +6 -8
  61. package/project/.sdk/src/cmp/go-cli/Main_go-cli.ts +6 -2
  62. package/project/.sdk/src/cmp/go-mcp/Main_go-mcp.ts +6 -2
  63. package/project/.sdk/src/cmp/java/Config_java.ts +8 -1
  64. package/project/.sdk/src/cmp/java/Test_java.ts +4 -7
  65. package/project/.sdk/src/cmp/js/Config_js.ts +9 -1
  66. package/project/.sdk/src/cmp/js/Package_js.ts +1 -1
  67. package/project/.sdk/src/cmp/js/Test_js.ts +4 -7
  68. package/project/.sdk/src/cmp/js/fragment/Config.data.fragment.js +7 -0
  69. package/project/.sdk/src/cmp/js/fragment/Config.fragment.js +8 -0
  70. package/project/.sdk/src/cmp/js/fragment/Main.fragment.js +33 -5
  71. package/project/.sdk/src/cmp/kotlin/Config_kotlin.ts +8 -1
  72. package/project/.sdk/src/cmp/kotlin/Test_kotlin.ts +4 -7
  73. package/project/.sdk/src/cmp/lean/Test_lean.ts +3 -3
  74. package/project/.sdk/src/cmp/lua/Config_lua.ts +11 -2
  75. package/project/.sdk/src/cmp/lua/Package_lua.ts +26 -3
  76. package/project/.sdk/src/cmp/lua/Test_lua.ts +5 -7
  77. package/project/.sdk/src/cmp/ocaml/Test_ocaml.ts +3 -7
  78. package/project/.sdk/src/cmp/perl/Config_perl.ts +8 -60
  79. package/project/.sdk/src/cmp/perl/Package_perl.ts +1 -1
  80. package/project/.sdk/src/cmp/perl/Test_perl.ts +5 -7
  81. package/project/.sdk/src/cmp/php/Config_php.ts +11 -2
  82. package/project/.sdk/src/cmp/php/EntityTypes_php.ts +13 -2
  83. package/project/.sdk/src/cmp/php/Main_php.ts +25 -0
  84. package/project/.sdk/src/cmp/php/Package_php.ts +2 -2
  85. package/project/.sdk/src/cmp/php/Test_php.ts +5 -7
  86. package/project/.sdk/src/cmp/php/fragment/Main.fragment.php +23 -6
  87. package/project/.sdk/src/cmp/py/Config_py.ts +11 -2
  88. package/project/.sdk/src/cmp/py/Main_py.ts +17 -7
  89. package/project/.sdk/src/cmp/py/Package_py.ts +2 -2
  90. package/project/.sdk/src/cmp/py/Test_py.ts +5 -7
  91. package/project/.sdk/src/cmp/py/fragment/Main.fragment.py +23 -6
  92. package/project/.sdk/src/cmp/py-data/Main_py-data.ts +14 -19
  93. package/project/.sdk/src/cmp/rb/Config_rb.ts +7 -1
  94. package/project/.sdk/src/cmp/rb/Package_rb.ts +3 -3
  95. package/project/.sdk/src/cmp/rb/ReadmeInstall_rb.ts +1 -1
  96. package/project/.sdk/src/cmp/rb/Test_rb.ts +5 -7
  97. package/project/.sdk/src/cmp/rust/Config_rust.ts +5 -2
  98. package/project/.sdk/src/cmp/rust/Test_rust.ts +5 -7
  99. package/project/.sdk/src/cmp/scala/Test_scala.ts +5 -2
  100. package/project/.sdk/src/cmp/seneca-provider/Gitignore_seneca-provider.ts +161 -0
  101. package/project/.sdk/src/cmp/seneca-provider/Main_seneca-provider.ts +20 -11
  102. package/project/.sdk/src/cmp/swift/Config_swift.ts +8 -54
  103. package/project/.sdk/src/cmp/swift/Package_swift.ts +52 -10
  104. package/project/.sdk/src/cmp/swift/Test_swift.ts +5 -7
  105. package/project/.sdk/src/cmp/ts/Config_ts.ts +9 -1
  106. package/project/.sdk/src/cmp/ts/EntityTypes_ts.ts +5 -2
  107. package/project/.sdk/src/cmp/ts/Entity_ts.ts +8 -2
  108. package/project/.sdk/src/cmp/ts/Package_ts.ts +1 -1
  109. package/project/.sdk/src/cmp/ts/Test_ts.ts +8 -7
  110. package/project/.sdk/src/cmp/ts/fragment/Config.data.fragment.ts +7 -0
  111. package/project/.sdk/src/cmp/ts/fragment/Config.fragment.ts +8 -0
  112. package/project/.sdk/src/cmp/ts/fragment/Entity.fragment.ts +1 -1
  113. package/project/.sdk/src/cmp/ts/fragment/Main.fragment.ts +12 -4
  114. package/project/.sdk/src/cmp/zig/Test_zig.ts +3 -7
  115. package/project/.sdk/tm/c/Makefile +6 -1
  116. package/project/.sdk/tm/c/utility/make_options.c +12 -1
  117. package/project/.sdk/tm/c/utility/make_point.c +64 -3
  118. package/project/.sdk/tm/clojure/src/sdk/core.clj +49 -14
  119. package/project/.sdk/tm/cpp/utility/pipeline.hpp +64 -4
  120. package/project/.sdk/tm/csharp/feature/TestFeature.cs +36 -2
  121. package/project/.sdk/tm/csharp/utility/Fetcher.cs +23 -5
  122. package/project/.sdk/tm/csharp/utility/MakeOptions.cs +13 -0
  123. package/project/.sdk/tm/csharp/utility/MakePoint.cs +65 -2
  124. package/project/.sdk/tm/dart/lib/feature/test/TestFeature.dart +34 -4
  125. package/project/.sdk/tm/dart/lib/utility/FetcherUtility.dart +9 -0
  126. package/project/.sdk/tm/dart/lib/utility/MakeOptionsUtility.dart +17 -0
  127. package/project/.sdk/tm/dart/lib/utility/MakePointUtility.dart +48 -2
  128. package/project/.sdk/tm/elixir/lib/projectname/utility.ex +109 -13
  129. package/project/.sdk/tm/elixir/test/feature_test.exs +19 -0
  130. package/project/.sdk/tm/elixir/test/primary_utility_test.exs +72 -0
  131. package/project/.sdk/tm/go/feature/test_feature.go +34 -2
  132. package/project/.sdk/tm/go/test/primary_utility_test.go +42 -20
  133. package/project/.sdk/tm/go/test/runner_test.go +1 -1
  134. package/project/.sdk/tm/go/utility/fetcher.go +14 -0
  135. package/project/.sdk/tm/go/utility/make_options.go +31 -4
  136. package/project/.sdk/tm/go/utility/make_point.go +66 -2
  137. package/project/.sdk/tm/java/feature/TestFeature.java +42 -2
  138. package/project/.sdk/tm/java/utility/Fetcher.java +11 -1
  139. package/project/.sdk/tm/java/utility/MakeOptions.java +11 -0
  140. package/project/.sdk/tm/java/utility/MakePoint.java +61 -2
  141. package/project/.sdk/tm/js/src/feature/test/TestFeature.js +22 -3
  142. package/project/.sdk/tm/js/src/utility/MakeOptionsUtility.js +20 -2
  143. package/project/.sdk/tm/js/src/utility/MakePointUtility.js +72 -2
  144. package/project/.sdk/tm/kotlin/feature/TestFeature.kt +31 -2
  145. package/project/.sdk/tm/kotlin/utility/Fetcher.kt +10 -1
  146. package/project/.sdk/tm/kotlin/utility/MakeOptions.kt +11 -0
  147. package/project/.sdk/tm/kotlin/utility/MakePoint.kt +58 -2
  148. package/project/.sdk/tm/lean/src/SdkUtility.lean +28 -0
  149. package/project/.sdk/tm/lean/test/TPrimaryUtility.lean +13 -2
  150. package/project/.sdk/tm/lua/utility/make_options.lua +44 -0
  151. package/project/.sdk/tm/lua/utility/make_point.lua +65 -2
  152. package/project/.sdk/tm/ocaml/sdk_runtime.ml +40 -2
  153. package/project/.sdk/tm/perl/utility/fetcher.pm +10 -1
  154. package/project/.sdk/tm/perl/utility/make_options.pm +36 -0
  155. package/project/.sdk/tm/perl/utility/make_point.pm +55 -2
  156. package/project/.sdk/tm/php/feature/TestFeature.php +30 -1
  157. package/project/.sdk/tm/php/test/PipelineTest.php +20 -0
  158. package/project/.sdk/tm/php/utility/Fetcher.php +12 -1
  159. package/project/.sdk/tm/php/utility/MakeOptions.php +58 -0
  160. package/project/.sdk/tm/php/utility/MakePoint.php +49 -1
  161. package/project/.sdk/tm/py/pkg/utility/fetcher.py +13 -1
  162. package/project/.sdk/tm/py/pkg/utility/make_options.py +23 -0
  163. package/project/.sdk/tm/py/pkg/utility/make_point.py +56 -2
  164. package/project/.sdk/tm/rb/utility/make_options.rb +18 -0
  165. package/project/.sdk/tm/rb/utility/make_point.rb +42 -2
  166. package/project/.sdk/tm/rust/tests/common/mod.rs +1 -1
  167. package/project/.sdk/tm/rust/tests/feature_test.rs +27 -0
  168. package/project/.sdk/tm/rust/utility/fetcher.rs +8 -0
  169. package/project/.sdk/tm/rust/utility/make_options.rs +15 -0
  170. package/project/.sdk/tm/rust/utility/make_point.rs +69 -2
  171. package/project/.sdk/tm/scala/feature/TestFeature.scala +36 -1
  172. package/project/.sdk/tm/scala/utility/Make.scala +57 -3
  173. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/feature/TestFeature.swift +29 -1
  174. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Fetcher.swift +24 -1
  175. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Make.swift +51 -2
  176. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/MakeOptions.swift +12 -0
  177. package/project/.sdk/tm/ts/src/feature/test/TestFeature.ts +22 -3
  178. package/project/.sdk/tm/ts/src/utility/MakeOptionsUtility.ts +20 -2
  179. package/project/.sdk/tm/ts/src/utility/MakePointUtility.ts +72 -2
  180. package/project/.sdk/tm/ts/test/utility/Corpus.test.ts +85 -0
  181. package/project/.sdk/tm/ts/test/utility/PrimaryUtility.test.ts +10 -17
  182. package/project/.sdk/tm/zig/core/utility.zig +66 -3
  183. package/project/sdkgen-package.json +1 -2
  184. package/src/cmp/AgentGuideTop.ts +18 -0
  185. package/src/cmp/ExternalTarget.ts +1 -1
  186. package/src/cmp/Readme.ts +2 -0
  187. package/src/cmp/ReadmeStation.ts +69 -0
  188. package/src/helpers/naming.ts +172 -5
  189. package/src/helpers/opShape.ts +85 -0
  190. package/src/helpers/packageMeta.ts +59 -20
  191. package/src/sdkgen.ts +70 -20
  192. package/src/testkit.ts +863 -0
  193. package/src/utility.ts +17 -2
  194. package/testkit.d.ts +3 -0
  195. package/testkit.js +12 -0
  196. package/project/.sdk/model/target/haskell.aontu +0 -72
  197. package/project/.sdk/src/cmp/haskell/Config_haskell.ts +0 -98
  198. package/project/.sdk/src/cmp/haskell/Entity_haskell.ts +0 -13
  199. package/project/.sdk/src/cmp/haskell/Gitignore_haskell.ts +0 -27
  200. package/project/.sdk/src/cmp/haskell/Main_haskell.ts +0 -123
  201. package/project/.sdk/src/cmp/haskell/Package_haskell.ts +0 -60
  202. package/project/.sdk/src/cmp/haskell/ReadmeEntity_haskell.ts +0 -186
  203. package/project/.sdk/src/cmp/haskell/ReadmeExamplesTest_haskell.ts +0 -135
  204. package/project/.sdk/src/cmp/haskell/ReadmeExplanation_haskell.ts +0 -48
  205. package/project/.sdk/src/cmp/haskell/ReadmeHowto_haskell.ts +0 -168
  206. package/project/.sdk/src/cmp/haskell/ReadmeInstall_haskell.ts +0 -51
  207. package/project/.sdk/src/cmp/haskell/ReadmeIntro_haskell.ts +0 -67
  208. package/project/.sdk/src/cmp/haskell/ReadmeModel_haskell.ts +0 -162
  209. package/project/.sdk/src/cmp/haskell/ReadmeOptions_haskell.ts +0 -74
  210. package/project/.sdk/src/cmp/haskell/ReadmeQuick_haskell.ts +0 -241
  211. package/project/.sdk/src/cmp/haskell/ReadmeRef_haskell.ts +0 -408
  212. package/project/.sdk/src/cmp/haskell/ReadmeTopHowto_haskell.ts +0 -31
  213. package/project/.sdk/src/cmp/haskell/ReadmeTopQuick_haskell.ts +0 -104
  214. package/project/.sdk/src/cmp/haskell/ReadmeTopTest_haskell.ts +0 -80
  215. package/project/.sdk/src/cmp/haskell/Test_haskell.ts +0 -300
  216. package/project/.sdk/src/cmp/haskell/tsconfig.json +0 -15
  217. package/project/.sdk/src/cmp/haskell/utility_haskell.ts +0 -166
  218. package/project/.sdk/tm/haskell/LICENSE +0 -21
  219. package/project/.sdk/tm/haskell/Makefile +0 -22
  220. package/project/.sdk/tm/haskell/VERSION +0 -1
  221. package/project/.sdk/tm/haskell/src/SdkFeatures.hs +0 -1440
  222. package/project/.sdk/tm/haskell/src/SdkHelpers.hs +0 -337
  223. package/project/.sdk/tm/haskell/src/SdkJson.hs +0 -111
  224. package/project/.sdk/tm/haskell/src/SdkRuntime.hs +0 -1219
  225. package/project/.sdk/tm/haskell/src/SdkTypes.hs +0 -195
  226. package/project/.sdk/tm/haskell/src/VoxgigStruct.hs +0 -2299
  227. package/project/.sdk/tm/haskell/src/Vregex.hs +0 -237
  228. package/project/.sdk/tm/haskell/src/feature/README.md +0 -5
  229. package/project/.sdk/tm/haskell/src/feature/audit/.gitkeep +0 -0
  230. package/project/.sdk/tm/haskell/src/feature/base/.gitkeep +0 -0
  231. package/project/.sdk/tm/haskell/src/feature/cache/.gitkeep +0 -0
  232. package/project/.sdk/tm/haskell/src/feature/clienttrack/.gitkeep +0 -0
  233. package/project/.sdk/tm/haskell/src/feature/debug/.gitkeep +0 -0
  234. package/project/.sdk/tm/haskell/src/feature/idempotency/.gitkeep +0 -0
  235. package/project/.sdk/tm/haskell/src/feature/log/.gitkeep +0 -0
  236. package/project/.sdk/tm/haskell/src/feature/metrics/.gitkeep +0 -0
  237. package/project/.sdk/tm/haskell/src/feature/netsim/.gitkeep +0 -0
  238. package/project/.sdk/tm/haskell/src/feature/paging/.gitkeep +0 -0
  239. package/project/.sdk/tm/haskell/src/feature/proxy/.gitkeep +0 -0
  240. package/project/.sdk/tm/haskell/src/feature/ratelimit/.gitkeep +0 -0
  241. package/project/.sdk/tm/haskell/src/feature/rbac/.gitkeep +0 -0
  242. package/project/.sdk/tm/haskell/src/feature/retry/.gitkeep +0 -0
  243. package/project/.sdk/tm/haskell/src/feature/streaming/.gitkeep +0 -0
  244. package/project/.sdk/tm/haskell/src/feature/telemetry/.gitkeep +0 -0
  245. package/project/.sdk/tm/haskell/src/feature/test/.gitkeep +0 -0
  246. package/project/.sdk/tm/haskell/src/feature/timeout/.gitkeep +0 -0
  247. package/project/.sdk/tm/haskell/test/Harness.hs +0 -281
  248. package/project/.sdk/tm/haskell/test/Runner.hs +0 -47
  249. package/project/.sdk/tm/haskell/test/StructCorpus.hs +0 -449
  250. package/project/.sdk/tm/haskell/test/TCustomUtility.hs +0 -38
  251. package/project/.sdk/tm/haskell/test/TFeature.hs +0 -794
  252. package/project/.sdk/tm/haskell/test/TNetsim.hs +0 -50
  253. package/project/.sdk/tm/haskell/test/TPipeline.hs +0 -334
  254. package/project/.sdk/tm/haskell/test/TPrimaryUtility.hs +0 -386
  255. package/project/.sdk/tm/haskell/test/Testutil.hs +0 -59
@@ -7,6 +7,33 @@ use crate::core::types::OutVal;
7
7
  use crate::utility::voxgigstruct as vs;
8
8
  use crate::utility::voxgigstruct::Value;
9
9
 
10
+ // How many path segments a point has.
11
+ fn parts_len(point: &Value) -> usize {
12
+ match getp(point, "parts") {
13
+ Value::List(l) => l.borrow().len(),
14
+ _ => 0,
15
+ }
16
+ }
17
+
18
+ // Does this point's path end in a parameter? A record route ends in the
19
+ // record's identifier (/boards/{id}); a cross-reference that also returns
20
+ // the entity ends in the relationship's name (/posts/{id}/author). That,
21
+ // then fewest segments, is what tells the entity's own route from a
22
+ // cross-reference. The same rule runs at generation time, in
23
+ // helpers/opShape.ts — both sides must move together.
24
+ fn terminal_param(point: &Value) -> bool {
25
+ match getp(point, "parts") {
26
+ Value::List(l) => {
27
+ let parts = l.borrow();
28
+ match parts.last() {
29
+ Some(Value::Str(s)) => s.starts_with('{'),
30
+ _ => false,
31
+ }
32
+ }
33
+ _ => false,
34
+ }
35
+ }
36
+
10
37
  pub fn make_point_util(ctx: &Rc<Context>) -> Result<Value, ProjectNameError> {
11
38
  match ctx.out_get("point") {
12
39
  // A PrePoint feature hook (e.g. rbac) may short-circuit the
@@ -60,9 +87,10 @@ pub fn make_point_util(ctx: &Rc<Context>) -> Result<Value, ProjectNameError> {
60
87
  };
61
88
 
62
89
  let mut point = Value::Noval;
90
+ let mut matched = false;
63
91
  for i in 0..plen {
64
- point = vs::get_elem(&points, &Value::Num(i as f64), Value::Noval);
65
- let select_def = to_map(&getp(&point, "select"));
92
+ let cand = vs::get_elem(&points, &Value::Num(i as f64), Value::Noval);
93
+ let select_def = to_map(&getp(&cand, "select"));
66
94
  let mut found = true;
67
95
 
68
96
  if !selector.is_noval() && !select_def.is_noval() {
@@ -89,10 +117,49 @@ pub fn make_point_util(ctx: &Rc<Context>) -> Result<Value, ProjectNameError> {
89
117
  }
90
118
 
91
119
  if found {
120
+ point = cand;
121
+ matched = true;
92
122
  break;
93
123
  }
94
124
  }
95
125
 
126
+ // select.exist can list more than the params needed to pick a point,
127
+ // so nothing matches — fall back to the entity's own route rather
128
+ // than whichever point came last.
129
+ if !matched {
130
+ // A request naming an action reaches here only because that
131
+ // action's own point failed its exist test, so it is unbuildable
132
+ // whatever we pick. Refuse it BEFORE choosing a fallback: the
133
+ // guard below compares the chosen point's $action and would wave
134
+ // the request through whenever the fallback lands on the action
135
+ // point itself.
136
+ let unmatched_action = getp(&reqselector, "$action");
137
+ if !unmatched_action.is_noval() {
138
+ return Err(ctx.make_error(
139
+ "point_action_invalid",
140
+ &format!(
141
+ "Operation \"{}\" action \"{}\" is not valid.",
142
+ op.name,
143
+ vs::stringify(&unmatched_action, None, false)
144
+ ),
145
+ ));
146
+ }
147
+
148
+ point = vs::get_elem(&points, &Value::Num(0.0), Value::Noval);
149
+ for i in 0..plen {
150
+ let cand = vs::get_elem(&points, &Value::Num(i as f64), Value::Noval);
151
+ let cand_term = terminal_param(&cand);
152
+ let best_term = terminal_param(&point);
153
+ if cand_term != best_term {
154
+ if cand_term {
155
+ point = cand;
156
+ }
157
+ } else if parts_len(&cand) < parts_len(&point) {
158
+ point = cand;
159
+ }
160
+ }
161
+ }
162
+
96
163
  let req_action = getp(&reqselector, "$action");
97
164
  if !req_action.is_noval() && !point.is_noval() {
98
165
  let point_select = to_map(&getp(&point, "select"));
@@ -215,9 +215,44 @@ class TestFeature extends BaseFeature("test", "0.0.1", true) {
215
215
  private def buildArgs(ctx: Context, op: Operation, args: JMap[String, Object]): Object = {
216
216
  val opname = op.name
217
217
 
218
+ // Pick the entity's own endpoint from config, not a cross-reference from
219
+ // another resource that also returns it — the same rule makePoint falls
220
+ // back to, so the seed-data query is built from the endpoint the request
221
+ // will actually be sent to: a terminal `{id}` marks a record route, and
222
+ // failing that the shallower path wins.
218
223
  val points = Struct.getpath(ctx.config,
219
224
  java.util.List.of("entity", if (ctx.entity == null) "" else ctx.entity.getName(), "op", opname, "points"))
220
- val point = Struct.getelem(points, java.lang.Integer.valueOf(-1))
225
+ var point = Struct.getelem(points, java.lang.Integer.valueOf(0))
226
+ points match {
227
+ case plist: java.util.List[_] =>
228
+ def pointPartsLen(p: Object): Int =
229
+ Struct.getprop(p, "parts") match {
230
+ case parts: java.util.List[_] => parts.size()
231
+ case _ => 0
232
+ }
233
+ def pointTerminalParam(p: Object): Boolean =
234
+ Struct.getprop(p, "parts") match {
235
+ case parts: java.util.List[_] if parts.size() > 0 =>
236
+ parts.get(parts.size() - 1) match {
237
+ case s: String => s.startsWith("{")
238
+ case _ => false
239
+ }
240
+ case _ => false
241
+ }
242
+ var k = 0
243
+ while (k < plist.size()) {
244
+ val cand = plist.get(k).asInstanceOf[Object]
245
+ val candTerm = pointTerminalParam(cand)
246
+ val bestTerm = pointTerminalParam(point)
247
+ if (candTerm != bestTerm) {
248
+ if (candTerm) point = cand
249
+ } else if (pointPartsLen(cand) < pointPartsLen(point)) {
250
+ point = cand
251
+ }
252
+ k += 1
253
+ }
254
+ case _ =>
255
+ }
221
256
 
222
257
  val paramsPath = Struct.getpath(point, java.util.List.of("args", "params"))
223
258
  val reqdParams = Struct.select(paramsPath, Struct.jm("reqd", java.lang.Boolean.TRUE))
@@ -94,11 +94,12 @@ object MakePoint {
94
94
  val selector: JMap[String, Object] = if ("data" == op.input) ctx.data else ctx.matchData
95
95
 
96
96
  var point: JMap[String, Object] = null
97
+ var matched = false
97
98
  var i = 0
98
99
  var break = false
99
100
  while (i < op.points.size() && !break) {
100
- point = op.points.get(i)
101
- val selectDef = Helpers.toMapAny(Struct.getprop(point, "select"))
101
+ val cand = op.points.get(i)
102
+ val selectDef = Helpers.toMapAny(Struct.getprop(cand, "select"))
102
103
  var found = true
103
104
 
104
105
  if (selector != null && selectDef != null) {
@@ -122,7 +123,60 @@ object MakePoint {
122
123
  if (!Objects.equals(reqAction, selectAction)) found = false
123
124
  }
124
125
 
125
- if (found) break = true else i += 1
126
+ if (found) { point = cand; matched = true; break = true } else i += 1
127
+ }
128
+
129
+ // select.exist can list more than the params needed to pick a point
130
+ // (for /boards/{id} it is Trello's 17 optional query-includes), so a
131
+ // plain {id} call matches NOTHING. Fall back to the entity's own route
132
+ // rather than whichever point came last.
133
+ if (!matched) {
134
+ // A request naming an action reaches here only because that action's
135
+ // own point failed its exist test, so it is unbuildable whatever we
136
+ // pick. Refuse it BEFORE choosing a fallback: the guard below
137
+ // compares the chosen point's $action and would wave the request
138
+ // through whenever the fallback lands on the action point itself.
139
+ val unmatchedAction =
140
+ if (reqselector != null) Struct.getprop(reqselector, "$action", null) else null
141
+ if (unmatchedAction != null) {
142
+ throw ctx.makeError("point_action_invalid",
143
+ "Operation \"" + op.name + "\" action \"" +
144
+ Struct.stringify(unmatchedAction) + "\" is not valid.")
145
+ }
146
+
147
+ // A terminal parameter marks a record route (/boards/{id}); a
148
+ // cross-reference ends in the relationship's name
149
+ // (/posts/{id}/author). Failing that, the shallower path wins. The
150
+ // same rule runs at generation time, in helpers/opShape.ts — both
151
+ // sides must move together.
152
+ def partsLen(p: JMap[String, Object]): Int =
153
+ Struct.getprop(p, "parts") match {
154
+ case parts: JList[_] => parts.size()
155
+ case _ => 0
156
+ }
157
+ def terminalParam(p: JMap[String, Object]): Boolean =
158
+ Struct.getprop(p, "parts") match {
159
+ case parts: JList[_] if parts.size() > 0 =>
160
+ parts.get(parts.size() - 1) match {
161
+ case s: String => s.startsWith("{")
162
+ case _ => false
163
+ }
164
+ case _ => false
165
+ }
166
+
167
+ point = op.points.get(0)
168
+ var j = 0
169
+ while (j < op.points.size()) {
170
+ val cand = op.points.get(j)
171
+ val candTerm = terminalParam(cand)
172
+ val bestTerm = terminalParam(point)
173
+ if (candTerm != bestTerm) {
174
+ if (candTerm) point = cand
175
+ } else if (partsLen(cand) < partsLen(point)) {
176
+ point = cand
177
+ }
178
+ j += 1
179
+ }
126
180
  }
127
181
 
128
182
  if (reqselector != null) {
@@ -38,7 +38,35 @@ private func testBuildArgs(_ ctx: Context, _ op: Operation, _ args: VMap?) -> Va
38
38
 
39
39
  let cfg: Value = ctx.config == nil ? .noval : .map(ctx.config!)
40
40
  let points = getpath(cfg, jtp("entity", ctx.entity!.getName(), "op", opname, "points"))
41
- let point = getelem(points, .int(-1))
41
+
42
+ // Pick the entity's own endpoint from config, not a cross-reference from
43
+ // another resource that also returns it — the same rule makePoint falls
44
+ // back to, so the seed-data query is built from the endpoint the request
45
+ // will actually be sent to: a terminal `{id}` marks a record route, and
46
+ // failing that the shallower path wins.
47
+ func pointPartsLen(_ p: Value) -> Int {
48
+ return getprop(p, .string("parts")).asList?.items.count ?? 0
49
+ }
50
+ func pointTerminalParam(_ p: Value) -> Bool {
51
+ guard let items = getprop(p, .string("parts")).asList?.items, let last = items.last else {
52
+ return false
53
+ }
54
+ return (last.asString ?? "").hasPrefix("{")
55
+ }
56
+
57
+ var point = getelem(points, .int(0))
58
+ if let candidates = points.asList?.items {
59
+ for cand in candidates {
60
+ let candTerm = pointTerminalParam(cand)
61
+ let bestTerm = pointTerminalParam(point)
62
+ if candTerm != bestTerm {
63
+ if candTerm { point = cand }
64
+ }
65
+ else if pointPartsLen(cand) < pointPartsLen(point) {
66
+ point = cand
67
+ }
68
+ }
69
+ }
42
70
 
43
71
  let paramsPath = getpath(point, jtp("args", "params"))
44
72
  let reqdParams = select(paramsPath, .map(vm(("reqd", .bool(true)))))
@@ -6,6 +6,27 @@ import Foundation
6
6
  import FoundationNetworking
7
7
  #endif
8
8
 
9
+ // Non-following twin of URLSession.shared. The station feature's
10
+ // middleware sets `redirect: manual` on the fetch definition under a
11
+ // hosts egress policy: a 3xx must come back as a response like any
12
+ // other, because an automatic follow would carry injected credentials
13
+ // to a Location no policy approved (the ts donor's fetch honours the
14
+ // same key natively; redirect policy is per-delegate in URLSession,
15
+ // hence a second session).
16
+ private final class ManualRedirectDelegate: NSObject, URLSessionTaskDelegate {
17
+ func urlSession(
18
+ _ session: URLSession, task: URLSessionTask,
19
+ willPerformHTTPRedirection response: HTTPURLResponse,
20
+ newRequest request: URLRequest,
21
+ completionHandler: @escaping (URLRequest?) -> Void
22
+ ) {
23
+ completionHandler(nil)
24
+ }
25
+ }
26
+
27
+ private let manualRedirectSession = URLSession(
28
+ configuration: .default, delegate: ManualRedirectDelegate(), delegateQueue: nil)
29
+
9
30
  func defaultHttpFetch(_ fullurl: String, _ fetchdef: VMap) throws -> Value {
10
31
  guard let url = URL(string: fullurl) else {
11
32
  let m = VMap()
@@ -45,7 +66,9 @@ func defaultHttpFetch(_ fullurl: String, _ fetchdef: VMap) throws -> Value {
45
66
  }
46
67
  let box = FetchBox()
47
68
  let sem = DispatchSemaphore(value: 0)
48
- let task = URLSession.shared.dataTask(with: req) { d, r, e in
69
+ let session = gp(fetchdef, "redirect").asString == "manual"
70
+ ? manualRedirectSession : URLSession.shared
71
+ let task = session.dataTask(with: req) { d, r, e in
49
72
  box.data = d; box.resp = r; box.err = e; sem.signal()
50
73
  }
51
74
  task.resume()
@@ -40,8 +40,8 @@ func makePointUtil(_ ctx: Context) throws -> VMap? {
40
40
  }
41
41
 
42
42
  var point: VMap? = nil
43
+ var matched = false
43
44
  for candidate in op.points {
44
- point = candidate
45
45
  let selectDef = gp(candidate, "select").asMap
46
46
  var found = true
47
47
 
@@ -62,7 +62,56 @@ func makePointUtil(_ ctx: Context) throws -> VMap? {
62
62
  if reqAction != selectAction { found = false }
63
63
  }
64
64
 
65
- if found { break }
65
+ if found {
66
+ point = candidate
67
+ matched = true
68
+ break
69
+ }
70
+ }
71
+
72
+ // select.exist can list more than the params needed to pick a point (for
73
+ // /boards/{id} it is Trello's 17 optional query-includes), so a plain
74
+ // {id} call matches NOTHING. Fall back to the entity's own route rather
75
+ // than whichever point came last.
76
+ if !matched {
77
+ // A request naming an action reaches here only because that action's
78
+ // own point failed its exist test, so it is unbuildable whatever we
79
+ // pick. Refuse it BEFORE choosing a fallback: the guard below compares
80
+ // the chosen point's $action and would wave the request through
81
+ // whenever the fallback lands on the action point itself.
82
+ let unmatchedAction = gp(reqselector, "$action")
83
+ if !isNil(unmatchedAction) {
84
+ throw ctx.makeError("point_action_invalid",
85
+ "Operation \"\(op.name)\" action \"\(stringify(unmatchedAction))\" is not valid.")
86
+ }
87
+
88
+ // A terminal parameter marks a record route (/boards/{id}); a
89
+ // cross-reference ends in the relationship's name (/posts/{id}/author).
90
+ // Failing that, the shallower path wins. The same rule runs at
91
+ // generation time, in helpers/opShape.ts — both sides must move
92
+ // together.
93
+ func partsLen(_ p: VMap) -> Int {
94
+ return gp(p, "parts").asList?.items.count ?? 0
95
+ }
96
+ func terminalParam(_ p: VMap) -> Bool {
97
+ guard let items = gp(p, "parts").asList?.items, let last = items.last else {
98
+ return false
99
+ }
100
+ return (last.asString ?? "").hasPrefix("{")
101
+ }
102
+
103
+ point = op.points.first
104
+ for candidate in op.points {
105
+ guard let best = point else { break }
106
+ let candTerm = terminalParam(candidate)
107
+ let bestTerm = terminalParam(best)
108
+ if candTerm != bestTerm {
109
+ if candTerm { point = candidate }
110
+ }
111
+ else if partsLen(candidate) < partsLen(best) {
112
+ point = candidate
113
+ }
114
+ }
66
115
  }
67
116
 
68
117
  let reqAction = gp(reqselector, "$action")
@@ -139,6 +139,18 @@ func makeOptionsUtil(_ ctx: Context) -> VMap {
139
139
  } else {
140
140
  for n in names { featureorder.append(.string(n)) }
141
141
  }
142
+
143
+ // Station special case, mirroring test's: its transport wrap must
144
+ // sit immediately outside the base transport (inside retry/cache/
145
+ // netsim), so map-form activation hoists it to just after test -
146
+ // or first, when no test entry exists. Without this the sorted
147
+ // default would init station last and wrap OUTSIDE the recording
148
+ // features, turning its wire-truth events into fiction.
149
+ if let si = featureorder.firstIndex(where: { $0.asString == "station" }) {
150
+ featureorder.remove(at: si)
151
+ let ti = featureorder.firstIndex(where: { $0.asString == "test" })
152
+ featureorder.insert(.string("station"), at: (ti ?? -1) + 1)
153
+ }
142
154
  }
143
155
 
144
156
  let derived = VMap()
@@ -253,9 +253,28 @@ class TestFeature extends BaseFeature {
253
253
  const isempty = struct.isempty
254
254
 
255
255
  const opname = getprop(op, 'name')
256
- const point =
257
- getelem(getpath(ctx.config, [
258
- 'entity', getprop(ctx.entity, 'name'), 'op', opname, 'points']), -1)
256
+ const points = getpath(ctx.config, [
257
+ 'entity', getprop(ctx.entity, 'name'), 'op', opname, 'points']) || []
258
+
259
+ // Pick the entity's own endpoint, not a cross-reference from another
260
+ // resource that also returns it — the same rule makePoint falls back to,
261
+ // so the seed-data query is built from the endpoint the request will
262
+ // actually be sent to: a terminal `{id}` marks a record route, and
263
+ // failing that the shallower path wins.
264
+ const isterm = (pt: any) => {
265
+ const parts = pt.parts
266
+ const last = 0 < parts.length ? parts[parts.length - 1] : ''
267
+ return 'string' === typeof last && 0 === last.indexOf('{')
268
+ }
269
+
270
+ let point = getelem(points, 0)
271
+ for (let i = 1; i < points.length; i++) {
272
+ const cand = getelem(points, i)
273
+ if (isterm(cand) !== isterm(point) ? isterm(cand) :
274
+ cand.parts.length < point.parts.length) {
275
+ point = cand
276
+ }
277
+ }
259
278
 
260
279
  const reqd = transform(
261
280
  select(getpath(point, ['args', 'params']), { reqd: true }),
@@ -71,6 +71,12 @@ function makeOptions(ctx: Context) {
71
71
  }
72
72
  },
73
73
  utility: {},
74
+ // Feature INSTANCES supplied at construction (the station adopt
75
+ // path): consumed by the constructor's featureAdd loop, so they are
76
+ // class instances, not data - `$ANY` accepts them verbatim. Without
77
+ // this entry the seam is dead: the constructor reads
78
+ // options.extend, but validate rejected the key.
79
+ extend: '`$ANY`' as any,
74
80
  system: {
75
81
  fetch: undefined as any
76
82
  },
@@ -134,10 +140,22 @@ function makeOptions(ctx: Context) {
134
140
  // otherwise order the map test-first, then the remaining names sorted, so
135
141
  // the outcome is deterministic and `test` is always the base transport.
136
142
  if (0 === featureorder.length) {
137
- const names = Object.keys(opts.feature || {}).sort()
138
- featureorder = names.indexOf('test') < 0
143
+ let names = Object.keys(opts.feature || {}).sort()
144
+ names = names.indexOf('test') < 0
139
145
  ? names
140
146
  : ['test'].concat(names.filter((n: string) => 'test' !== n))
147
+ // Station special case, mirroring test's: its transport wrap must
148
+ // sit immediately outside the base transport (inside retry/cache/
149
+ // netsim), so map-form activation hoists it to just after test -
150
+ // or first, when no test entry exists. Without this the sorted
151
+ // default would init station last and wrap OUTSIDE the recording
152
+ // features, turning its wire-truth events into fiction.
153
+ const si = names.indexOf('station')
154
+ if (0 <= si) {
155
+ names.splice(si, 1)
156
+ names.splice(names.indexOf('test') + 1, 0, 'station')
157
+ }
158
+ featureorder = names
141
159
  }
142
160
 
143
161
  opts.__derived__ = {
@@ -2,6 +2,52 @@
2
2
  import { Context, Point } from '../types'
3
3
 
4
4
 
5
+ // The entity's OWN route among an op's points, as opposed to a
6
+ // CROSS-REFERENCE: another resource's route that happens to return this
7
+ // entity (`/notifications/{id}/board` for a board). Both are plain GETs
8
+ // with no `$action`, so the path itself is all there is to go on.
9
+ //
10
+ // Two signals, in order:
11
+ //
12
+ // 1. A record route ends in the record's identifier (`/boards/{id}`,
13
+ // `/accounts/{account_id}/users/{id}`); a cross-reference ends in the
14
+ // relationship's name (`/posts/{id}/author`). A terminal parameter is
15
+ // therefore the stronger signal, and unlike depth it survives an entity
16
+ // nested more deeply than the route pointing at it.
17
+ // 2. Failing that, fewest path segments — the shallower route is the one
18
+ // the entity is named for.
19
+ //
20
+ // Ties keep the earlier point, so the sorted-key order of the model decides
21
+ // and the output stays byte-stable.
22
+ //
23
+ // The same rule runs at GENERATION time, in `shortestPoint`/`ownPoint` in
24
+ // helpers/opShape.ts. It cannot be shared with this file — a template ships
25
+ // standalone, outside that package — so it is written twice on purpose, and
26
+ // both sides must move together.
27
+ function terminalParam(point: any): boolean {
28
+ const parts = point.parts
29
+ const last = 0 < parts.length ? parts[parts.length - 1] : ''
30
+ return 'string' === typeof last && 0 === last.indexOf('{')
31
+ }
32
+
33
+
34
+ function ownPoint(points: any[]): any {
35
+ let best = points[0]
36
+
37
+ for (const cand of points) {
38
+ const candterm = terminalParam(cand)
39
+ const bestterm = terminalParam(best)
40
+
41
+ if (candterm !== bestterm ? candterm :
42
+ cand.parts.length < best.parts.length) {
43
+ best = cand
44
+ }
45
+ }
46
+
47
+ return best
48
+ }
49
+
50
+
5
51
  function makePoint(ctx: Context): Point | Error {
6
52
  if (ctx.out.point) {
7
53
  return ctx.point = ctx.out.point
@@ -31,9 +77,10 @@ function makePoint(ctx: Context): Point | Error {
31
77
  const selector = getprop(ctx, op.input)
32
78
 
33
79
  let point
80
+ let matched = false
34
81
  for (let i = 0; i < op.points.length; i++) {
35
- point = op.points[i]
36
- const select = point.select
82
+ const cand = op.points[i]
83
+ const select = cand.select
37
84
  let found = true
38
85
 
39
86
  if (selector && select.exist) {
@@ -56,10 +103,33 @@ function makePoint(ctx: Context): Point | Error {
56
103
  }
57
104
 
58
105
  if (found) {
106
+ point = cand
107
+ matched = true
59
108
  break
60
109
  }
61
110
  }
62
111
 
112
+ // select.exist can list more than the params needed to pick a point (for
113
+ // /boards/{id} it is Trello's 17 optional query-includes), so a plain
114
+ // {id} call matches NOTHING. Fall back to the entity's OWN route rather
115
+ // than whichever point came last.
116
+ if (!matched) {
117
+ // A request naming an action reaches here only because that action's
118
+ // own point failed its exist test, so the request is unbuildable
119
+ // whatever we pick. Refuse it BEFORE choosing a fallback: the guard
120
+ // below compares the chosen point's $action, and would wave the
121
+ // request through whenever the action point is itself the one the
122
+ // fallback selects (a short action route folded into an entity whose
123
+ // canonical route is longer). An unbuildable action request is an
124
+ // error, never a silent request to some other endpoint.
125
+ if (null != reqselector.$action) {
126
+ return ctx.error('point_action_invalid', 'Operation "' + op.name +
127
+ '" action "' + reqselector.$action + '" is not valid.')
128
+ }
129
+
130
+ point = ownPoint(op.points)
131
+ }
132
+
63
133
  if (
64
134
  null != reqselector.$action &&
65
135
  null != point &&
@@ -0,0 +1,85 @@
1
+ import { test, describe } from 'node:test'
2
+ import { ok, equal } from 'node:assert'
3
+ import { readFileSync } from 'node:fs'
4
+ import { join } from 'node:path'
5
+
6
+ import { TEST_JSON_FILE } from './index'
7
+
8
+
9
+ // Guards the shared corpus AS A WHOLE, which the per-section guard in the
10
+ // language runners cannot do.
11
+ //
12
+ // That guard only fires for a section some test actually runs. Seven sections
13
+ // — fetcher, makeFetchDef, makePoint, makeResult, featureAdd, featureHook and
14
+ // featureInit — had no test calling them at all, so they sat at `set: []`
15
+ // through two reviews reporting nothing. A fixture nobody runs is
16
+ // indistinguishable from a fixture that passes.
17
+ //
18
+ // Deferral is therefore DATA (`basic.pending`), not a comment: comments do not
19
+ // survive compilation to test.json, so a marker written only in the .aontu
20
+ // source cannot be checked by the thing that consumes it. Being data, these
21
+ // invariants hold for every port, not just this one.
22
+ describe('Corpus', () => {
23
+
24
+ // Resolved the same way runner.ts resolves it — from dist-test/, one level
25
+ // up from this file's dist-test/utility/.
26
+ const corpus = JSON.parse(
27
+ readFileSync(join(__dirname, '..', TEST_JSON_FILE), 'utf8'))
28
+
29
+ const primary: Record<string, any> = corpus.primary || {}
30
+ const names = Object.keys(primary).sort()
31
+
32
+
33
+ test('the corpus is not empty', () => {
34
+ ok(0 < names.length, 'no primary sections found — check TEST_JSON_FILE')
35
+ })
36
+
37
+
38
+ // A section that compiles to zero cases must say why, in the fixture.
39
+ test('every empty section declares why it is deferred', () => {
40
+ const undeclared = names.filter((n) => {
41
+ const basic = primary[n]?.basic
42
+ return Array.isArray(basic?.set) && 0 === basic.set.length &&
43
+ 'string' !== typeof basic?.pending
44
+ })
45
+ equal(undeclared.join(','), '',
46
+ 'these sections compile to ZERO cases and carry no `basic: pending` ' +
47
+ 'reason — add cases, or state the blocker in .sdk/test/primary/<name>.aontu')
48
+ })
49
+
50
+
51
+ // "PENDING" with no reason is just a way to keep a hole open.
52
+ test('every deferral gives a real reason', () => {
53
+ for (const n of names) {
54
+ const pending = primary[n]?.basic?.pending
55
+ if (null == pending) continue
56
+ ok(20 < String(pending).length,
57
+ `${n}: 'pending' needs a reason, not just a marker`)
58
+ }
59
+ })
60
+
61
+
62
+ // The other direction: a section that has gained cases must drop its
63
+ // deferral, or the marker rots into a lie and the runners keep excusing it.
64
+ test('a deferred section that gained cases is promoted', () => {
65
+ const stale = names.filter((n) => {
66
+ const basic = primary[n]?.basic
67
+ return null != basic?.pending && Array.isArray(basic?.set) &&
68
+ 0 < basic.set.length
69
+ })
70
+ equal(stale.join(','), '',
71
+ 'these sections now have cases — remove `basic: pending` from the ' +
72
+ 'fixture and from the PENDING list in the language runners')
73
+ })
74
+
75
+
76
+ // Every section must actually be a section: `basic.set` is what the runners
77
+ // iterate, and a fixture that failed to compile silently loses it.
78
+ test('every section has a basic.set list', () => {
79
+ for (const n of names) {
80
+ ok(Array.isArray(primary[n]?.basic?.set),
81
+ `${n}: no basic.set list — the fixture did not compile as expected`)
82
+ }
83
+ })
84
+
85
+ })