@enricai/barnacle 1.12.18 → 1.12.20

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 (45) hide show
  1. package/README.md +32 -1211
  2. package/dist/config.d.ts +8 -0
  3. package/dist/config.d.ts.map +1 -1
  4. package/dist/config.js +3 -0
  5. package/dist/config.js.map +1 -1
  6. package/dist/lib/merge-folded-primary-bodies.d.ts +10 -0
  7. package/dist/lib/merge-folded-primary-bodies.d.ts.map +1 -0
  8. package/dist/lib/merge-folded-primary-bodies.js +29 -0
  9. package/dist/lib/merge-folded-primary-bodies.js.map +1 -0
  10. package/dist/plugins/loader.d.ts.map +1 -1
  11. package/dist/plugins/loader.js +4 -1
  12. package/dist/plugins/loader.js.map +1 -1
  13. package/dist/scraper/errors.d.ts +7 -7
  14. package/dist/scraper/errors.js +7 -7
  15. package/dist/scraper/fake-timer-advance.d.ts +35 -8
  16. package/dist/scraper/fake-timer-advance.d.ts.map +1 -1
  17. package/dist/scraper/fake-timer-advance.js +29 -9
  18. package/dist/scraper/fake-timer-advance.js.map +1 -1
  19. package/dist/scraper/flow-runner.d.ts.map +1 -1
  20. package/dist/scraper/flow-runner.js +11 -13
  21. package/dist/scraper/flow-runner.js.map +1 -1
  22. package/dist/scraper/http-client.d.ts +1 -1
  23. package/dist/scraper/http-client.js +1 -1
  24. package/dist/scraper/pool.d.ts +3 -3
  25. package/dist/scraper/pool.js +5 -5
  26. package/dist/scripts/recon-browser.d.ts +9 -1
  27. package/dist/scripts/recon-browser.d.ts.map +1 -1
  28. package/dist/scripts/recon-browser.js +35 -1
  29. package/dist/scripts/recon-browser.js.map +1 -1
  30. package/dist/scripts/recon-generate-multicall-fixture.d.ts +505 -0
  31. package/dist/scripts/recon-generate-multicall-fixture.d.ts.map +1 -1
  32. package/dist/scripts/recon-generate-multicall-fixture.js +1440 -1
  33. package/dist/scripts/recon-generate-multicall-fixture.js.map +1 -1
  34. package/dist/scripts/recon-generate.d.ts +215 -6
  35. package/dist/scripts/recon-generate.d.ts.map +1 -1
  36. package/dist/scripts/recon-generate.js +1573 -49
  37. package/dist/scripts/recon-generate.js.map +1 -1
  38. package/dist/scripts/recon-heal.d.ts +3 -3
  39. package/dist/scripts/recon-heal.js +4 -4
  40. package/dist/scripts/recon-heal.js.map +1 -1
  41. package/dist/server.js +1 -1
  42. package/dist/server.js.map +1 -1
  43. package/dist/site-plugin.d.ts +4 -3
  44. package/dist/site-plugin.d.ts.map +1 -1
  45. package/package.json +2 -2
@@ -4,7 +4,45 @@ exports.buildCapture = buildCapture;
4
4
  exports.buildStep = buildStep;
5
5
  exports.buildMulticallHeterogeneousActionSteps = buildMulticallHeterogeneousActionSteps;
6
6
  exports.buildMulticallHeterogeneousActionStepsWithDrillDown = buildMulticallHeterogeneousActionStepsWithDrillDown;
7
+ exports.buildMulticallDependentDrillDownActionSteps = buildMulticallDependentDrillDownActionSteps;
7
8
  exports.buildWizardCheckoutCaptures = buildWizardCheckoutCaptures;
9
+ exports.buildMulticallSingleShotSearchDrillDownActionSteps = buildMulticallSingleShotSearchDrillDownActionSteps;
10
+ exports.buildMulticallSingleShotSearchDrillDownDrillDecoyActionSteps = buildMulticallSingleShotSearchDrillDownDrillDecoyActionSteps;
11
+ exports.buildMulticallSingleShotSearchDrillDownNoDecoyActionSteps = buildMulticallSingleShotSearchDrillDownNoDecoyActionSteps;
12
+ exports.buildMulticallSingleShotSearchDrillDownNestedJoinFieldMultiItemActionSteps = buildMulticallSingleShotSearchDrillDownNestedJoinFieldMultiItemActionSteps;
13
+ exports.buildMulticallSingleShotSearchDrillDownRicherFlatOutranksNestedArrayActionSteps = buildMulticallSingleShotSearchDrillDownRicherFlatOutranksNestedArrayActionSteps;
14
+ exports.buildMulticallSingleShotSearchTwoIndependentDrillDownsActionSteps = buildMulticallSingleShotSearchTwoIndependentDrillDownsActionSteps;
15
+ exports.buildMulticallSingleShotSearchTwoIndependentDrillDownsSharedJoinFieldActionSteps = buildMulticallSingleShotSearchTwoIndependentDrillDownsSharedJoinFieldActionSteps;
16
+ exports.buildMulticallTwoIndependentPrimariesActionSteps = buildMulticallTwoIndependentPrimariesActionSteps;
17
+ exports.buildMulticallTwoIndependentPrimariesSecondHeaderThreadedActionSteps = buildMulticallTwoIndependentPrimariesSecondHeaderThreadedActionSteps;
18
+ exports.buildMulticallSingleShotSearchHeuristicAndSpecTwoTargetActionSteps = buildMulticallSingleShotSearchHeuristicAndSpecTwoTargetActionSteps;
19
+ exports.buildMulticallTwoOccurrencesSamePrimaryDistinctDrillsActionSteps = buildMulticallTwoOccurrencesSamePrimaryDistinctDrillsActionSteps;
20
+ exports.buildMulticallNestedGroupedDrillDownMultiGroupActionSteps = buildMulticallNestedGroupedDrillDownMultiGroupActionSteps;
21
+ exports.buildMulticallSingleShotSearchDrillDownNumericJoinActionSteps = buildMulticallSingleShotSearchDrillDownNumericJoinActionSteps;
22
+ exports.buildMulticallSingleShotSearchDrillDownPathThreadedJoinActionSteps = buildMulticallSingleShotSearchDrillDownPathThreadedJoinActionSteps;
23
+ exports.buildMulticallSingleShotSearchDrillDownNestedJoinFieldActionSteps = buildMulticallSingleShotSearchDrillDownNestedJoinFieldActionSteps;
24
+ exports.buildMulticallSingleShotSearchDrillDownCompositeNumericJoinActionSteps = buildMulticallSingleShotSearchDrillDownCompositeNumericJoinActionSteps;
25
+ exports.buildMulticallSingleShotSearchDrillDownNonFirstItemSkuActionSteps = buildMulticallSingleShotSearchDrillDownNonFirstItemSkuActionSteps;
26
+ exports.buildMulticallSingleShotSearchDrillDownCompositeNumericJoinNonFirstItemActionSteps = buildMulticallSingleShotSearchDrillDownCompositeNumericJoinNonFirstItemActionSteps;
27
+ exports.buildMulticallSingleShotSearchDrillDownHeaderThreadedJoinActionSteps = buildMulticallSingleShotSearchDrillDownHeaderThreadedJoinActionSteps;
28
+ exports.buildMulticallSingleShotSearchDrillDownHeaderThreadedJoinRequeriedPrimaryOverlapActionSteps = buildMulticallSingleShotSearchDrillDownHeaderThreadedJoinRequeriedPrimaryOverlapActionSteps;
29
+ exports.buildMulticallSingleShotSearchDrillDownHeaderThreadedJoinMultiItemActionSteps = buildMulticallSingleShotSearchDrillDownHeaderThreadedJoinMultiItemActionSteps;
30
+ exports.buildMulticallSingleShotSearchDrillDownOutOfOrderItemActionSteps = buildMulticallSingleShotSearchDrillDownOutOfOrderItemActionSteps;
31
+ exports.buildMulticallSingleShotSearchDrillDownMultiMatchActionSteps = buildMulticallSingleShotSearchDrillDownMultiMatchActionSteps;
32
+ exports.buildMulticallSingleShotSearchDrillDownTypeMismatchJoinActionSteps = buildMulticallSingleShotSearchDrillDownTypeMismatchJoinActionSteps;
33
+ exports.buildMulticallSingleShotSearchDrillDownChainedDependentMultipartChainStepActionSteps = buildMulticallSingleShotSearchDrillDownChainedDependentMultipartChainStepActionSteps;
34
+ exports.buildMulticallSingleShotSearchDrillDownChainedDependentActionSteps = buildMulticallSingleShotSearchDrillDownChainedDependentActionSteps;
35
+ exports.buildMulticallSingleShotSearchDrillDownNestedJoinFieldChainedDependentActionSteps = buildMulticallSingleShotSearchDrillDownNestedJoinFieldChainedDependentActionSteps;
36
+ exports.buildMulticallSingleShotSearchDrillDownRequeriedPrimaryOverlapActionSteps = buildMulticallSingleShotSearchDrillDownRequeriedPrimaryOverlapActionSteps;
37
+ exports.buildMulticallSingleShotSearchTwoIndependentArraysActionSteps = buildMulticallSingleShotSearchTwoIndependentArraysActionSteps;
38
+ exports.buildMulticallStructuralPlusSpecOnlySameStepActionSteps = buildMulticallStructuralPlusSpecOnlySameStepActionSteps;
39
+ exports.buildMulticallSingleShotSearchDrillDownChainedDecoyOnChainTerminalActionSteps = buildMulticallSingleShotSearchDrillDownChainedDecoyOnChainTerminalActionSteps;
40
+ exports.buildMulticallSingleShotSearchDrillDownOpaqueIntermediateChainedDependentActionSteps = buildMulticallSingleShotSearchDrillDownOpaqueIntermediateChainedDependentActionSteps;
41
+ exports.buildMulticallSingleShotSearchDrillDownRichnessTiedConfirmationHopChainedDependentActionSteps = buildMulticallSingleShotSearchDrillDownRichnessTiedConfirmationHopChainedDependentActionSteps;
42
+ exports.buildMulticallSingleShotSearchDrillDownArrayWrappedChainedDependentActionSteps = buildMulticallSingleShotSearchDrillDownArrayWrappedChainedDependentActionSteps;
43
+ exports.buildMulticallSingleShotSearchDrillDownArrayWrappedNumericChainedJoinFieldActionSteps = buildMulticallSingleShotSearchDrillDownArrayWrappedNumericChainedJoinFieldActionSteps;
44
+ exports.buildMulticallSingleShotSearchDrillDownArrayWrappedImmediateJoinFieldActionSteps = buildMulticallSingleShotSearchDrillDownArrayWrappedImmediateJoinFieldActionSteps;
45
+ exports.buildMulticallSingleShotSearchDrillDownArrayWrappedNumericImmediateJoinFieldActionSteps = buildMulticallSingleShotSearchDrillDownArrayWrappedNumericImmediateJoinFieldActionSteps;
8
46
  function buildCapture(overrides) {
9
47
  return {
10
48
  timestamp: overrides.timestamp,
@@ -12,7 +50,7 @@ function buildCapture(overrides) {
12
50
  method: "POST",
13
51
  url: overrides.url,
14
52
  status: 200,
15
- requestHeaders: { "Content-Type": "application/json" },
53
+ requestHeaders: overrides.requestHeaders ?? { "Content-Type": "application/json" },
16
54
  requestPostData: overrides.requestPostData,
17
55
  responseHeaders: { "content-type": "application/json" },
18
56
  responseBody: overrides.responseBody,
@@ -102,6 +140,53 @@ function buildMulticallHeterogeneousActionStepsWithDrillDown() {
102
140
  }),
103
141
  ];
104
142
  }
143
+ const CATALOG_SEARCH_URL = "https://api.example.com/catalog/search/";
144
+ const CATALOG_ITEM_DETAIL_URL = "https://api.example.com/catalog/item-detail/";
145
+ /**
146
+ * Reproduces a search-then-per-item-drill-down flow whose primary page
147
+ * carries multiple results, each followed by its own drill-down call — the
148
+ * shape {@link detectDrillDownFoldPlan} must resolve by join key rather than
149
+ * by position. `search`/`page 2` is re-queried (distinct body from `page 1`)
150
+ * so it satisfies {@link findRequeriedActions}'s relevance signal, and its
151
+ * response holds two items (`i-b` then `i-a`, deliberately not alphabetical)
152
+ * so a later merge can't assume primary-array order. The two drill-down
153
+ * calls are fired in the OPPOSITE order of the primary array (`i-a`'s
154
+ * drill-down first, `i-b`'s second) — a positional/index-based fold would
155
+ * pair the wrong item with the wrong drill-down response, while a join-key
156
+ * fold (matching each drill request's `itemId` back to the primary item that
157
+ * produced it) pairs them correctly regardless of call order.
158
+ */
159
+ function buildMulticallDependentDrillDownActionSteps() {
160
+ return [
161
+ buildStep("r0", {
162
+ url: CATALOG_SEARCH_URL,
163
+ requestPostData: '{"page":1}',
164
+ responseBody: { totalPages: 2, items: [{ itemId: "solo" }] },
165
+ timestamp: "2024-03-01T00:00:00Z",
166
+ }),
167
+ buildStep("r1", {
168
+ url: CATALOG_SEARCH_URL,
169
+ requestPostData: '{"page":2}',
170
+ responseBody: {
171
+ totalPages: 2,
172
+ items: [{ itemId: "i-b" }, { itemId: "i-a" }],
173
+ },
174
+ timestamp: "2024-03-01T00:00:01Z",
175
+ }),
176
+ buildStep("r2", {
177
+ url: CATALOG_ITEM_DETAIL_URL,
178
+ requestPostData: '{"itemId":"i-a"}',
179
+ responseBody: { details: [{ detailId: "d-a" }] },
180
+ timestamp: "2024-03-01T00:00:02Z",
181
+ }),
182
+ buildStep("r3", {
183
+ url: CATALOG_ITEM_DETAIL_URL,
184
+ requestPostData: '{"itemId":"i-b"}',
185
+ responseBody: { details: [{ detailId: "d-b" }] },
186
+ timestamp: "2024-03-01T00:00:03Z",
187
+ }),
188
+ ];
189
+ }
105
190
  const CHECKOUT_HOST = "https://api.example.com";
106
191
  /**
107
192
  * Reproduces a wizard-style checkout submission on one host: a create-order
@@ -146,4 +231,1358 @@ function buildWizardCheckoutCaptures() {
146
231
  }),
147
232
  ];
148
233
  }
234
+ const CATALOG_PRICING_URL = "https://api.example.com/catalog/pricing/";
235
+ /**
236
+ * A search → per-item drill-down flow whose search endpoint fires exactly
237
+ * ONCE. {@link detectDrillDownFoldPlan} now scans every prior action as a
238
+ * fold-primary candidate, so a single-shot search is just as eligible as a
239
+ * re-queried one — but the primary response here also carries a decoy
240
+ * `facets[]` array ahead of the real `results[]`, so `findObjectArrayField`'s
241
+ * DFS first-match deliberately disagrees with the array a caller would fold
242
+ * onto. `resolveFoldPlan(steps)` on this fixture still returns `null`, but
243
+ * only because of that decoy, not because the search fires once. That
244
+ * decoy-vs-declared-path gap is the shape the flow-declared `foldReturn`
245
+ * (`FoldReturnSpec`/`resolveFoldPlan`) exists to reach.
246
+ *
247
+ * The decoy is load-bearing for a `resolveFoldPlan`/`foldReturn` test
248
+ * elsewhere; {@link buildMulticallSingleShotSearchDrillDownNoDecoyActionSteps}
249
+ * is the decoy-free sibling used to prove `detectDrillDownFoldPlan` alone
250
+ * detects a single-shot primary.
251
+ */
252
+ function buildMulticallSingleShotSearchDrillDownActionSteps() {
253
+ return [
254
+ buildStep("r0", {
255
+ url: CATALOG_SEARCH_URL,
256
+ requestPostData: '{"page":1}',
257
+ responseBody: {
258
+ facets: [{ name: "brand" }],
259
+ results: [{ sku: "sku-a" }, { sku: "sku-b" }],
260
+ },
261
+ timestamp: "2024-04-01T00:00:00Z",
262
+ }),
263
+ buildStep("r1", {
264
+ url: CATALOG_PRICING_URL,
265
+ requestPostData: '{"sku":"sku-a"}',
266
+ responseBody: { prices: [{ sku: "sku-a", amount: 19.99 }] },
267
+ timestamp: "2024-04-01T00:00:01Z",
268
+ }),
269
+ ];
270
+ }
271
+ /**
272
+ * A search → per-item drill-down flow whose search response is unambiguous
273
+ * (a single `results[]` array, no decoy), but whose DRILL step's response
274
+ * carries a decoy `errors[]` array ahead of the real per-item `details[]`
275
+ * array. `findObjectArrayField`'s DFS first-match lands on `errors[]`
276
+ * instead of `details[]` when resolving the drill side of the fold, so a
277
+ * `foldReturn` declaration that only names the primary's `resultsPath`
278
+ * (see {@link FoldReturnSpec}) cannot fix this — the gap is on the drill
279
+ * side, not the primary side. Proves a drill-side results-path declaration
280
+ * (as opposed to {@link buildMulticallSingleShotSearchDrillDownActionSteps}'s
281
+ * primary-side decoy) is what a `drillResultsPath`-style flow declaration
282
+ * exists to reach.
283
+ */
284
+ function buildMulticallSingleShotSearchDrillDownDrillDecoyActionSteps() {
285
+ return [
286
+ buildStep("r0", {
287
+ url: CATALOG_SEARCH_URL,
288
+ requestPostData: '{"page":1}',
289
+ responseBody: {
290
+ results: [{ sku: "sku-a" }, { sku: "sku-b" }],
291
+ },
292
+ timestamp: "2024-04-01T00:00:00Z",
293
+ }),
294
+ buildStep("r1", {
295
+ url: CATALOG_PRICING_URL,
296
+ requestPostData: '{"sku":"sku-a"}',
297
+ responseBody: {
298
+ errors: [{ code: "none" }],
299
+ details: [{ sku: "sku-a", price: 19.99 }],
300
+ },
301
+ timestamp: "2024-04-01T00:00:01Z",
302
+ }),
303
+ ];
304
+ }
305
+ /**
306
+ * A decoy-free sibling of {@link buildMulticallSingleShotSearchDrillDownActionSteps}:
307
+ * the search endpoint still fires exactly once, but `results[]` is the sole
308
+ * object-array field in its response, so `findObjectArrayField`'s DFS
309
+ * first-match lands on the real results array with no declared `foldReturn`
310
+ * needed. Proves the single-shot primary is detected on structure alone.
311
+ */
312
+ function buildMulticallSingleShotSearchDrillDownNoDecoyActionSteps() {
313
+ return [
314
+ buildStep("r0", {
315
+ url: CATALOG_SEARCH_URL,
316
+ requestPostData: '{"page":1}',
317
+ responseBody: {
318
+ results: [{ sku: "sku-a" }, { sku: "sku-b" }],
319
+ },
320
+ timestamp: "2024-04-01T00:00:00Z",
321
+ }),
322
+ buildStep("r1", {
323
+ url: CATALOG_PRICING_URL,
324
+ requestPostData: '{"sku":"sku-a"}',
325
+ responseBody: { prices: [{ sku: "sku-a", amount: 19.99 }] },
326
+ timestamp: "2024-04-01T00:00:01Z",
327
+ }),
328
+ ];
329
+ }
330
+ /**
331
+ * A nested-join-key sibling of {@link buildMulticallSingleShotSearchDrillDownNoDecoyActionSteps}
332
+ * with a SECOND primary item, so a per-item fold loop is actually exercised
333
+ * (not just a single-item detection check): each primary `results[]` item
334
+ * carries its join key under a nested `identifiers` object
335
+ * (`{ identifiers: { sku } }`) instead of as a top-level field, while the
336
+ * drill-down request is still keyed by the plain `sku` value pulled out of
337
+ * that nested field. Proves the fold loop threads a join key found by
338
+ * walking INTO an item's nested objects, not only its own top-level
339
+ * `Object.entries`, across every primary item.
340
+ */
341
+ function buildMulticallSingleShotSearchDrillDownNestedJoinFieldMultiItemActionSteps() {
342
+ return [
343
+ buildStep("r0", {
344
+ url: CATALOG_SEARCH_URL,
345
+ requestPostData: '{"page":1}',
346
+ responseBody: {
347
+ results: [{ identifiers: { sku: "sku-a" } }, { identifiers: { sku: "sku-b" } }],
348
+ },
349
+ timestamp: "2024-04-01T00:00:00Z",
350
+ }),
351
+ buildStep("r1", {
352
+ url: CATALOG_PRICING_URL,
353
+ requestPostData: '{"sku":"sku-a"}',
354
+ responseBody: { prices: [{ sku: "sku-a", amount: 19.99 }] },
355
+ timestamp: "2024-04-01T00:00:01Z",
356
+ }),
357
+ ];
358
+ }
359
+ /**
360
+ * A search → per-item drill-down flow whose DRILL step's response carries
361
+ * BOTH a small real nested object-array field (`tags[]`, one primitive
362
+ * field per item, no join echo) AND richer flat top-level per-item fields
363
+ * (`name`/`price`/`brand`, none of them echoing the join either) — the
364
+ * shape {@link findAllObjectArrayFieldsOrWholeObject}'s old docstring
365
+ * ("a response that already has one [a real object-array field] is never
366
+ * second-guessed") resolved to the tiny `tags[]` array instead of the
367
+ * richer flat object. Neither candidate threads the join value in its
368
+ * response body (only the drill REQUEST carries `sku-a`), so selection must
369
+ * fall through to comparing per-item primitive-field richness, which the
370
+ * flat object wins 3 (`name`/`price`/`brand`) to 1 (`tags[0].id`).
371
+ */
372
+ function buildMulticallSingleShotSearchDrillDownRicherFlatOutranksNestedArrayActionSteps() {
373
+ return [
374
+ buildStep("r0", {
375
+ url: CATALOG_SEARCH_URL,
376
+ requestPostData: '{"page":1}',
377
+ responseBody: {
378
+ results: [{ sku: "sku-a" }, { sku: "sku-b" }],
379
+ },
380
+ timestamp: "2024-04-01T00:00:00Z",
381
+ }),
382
+ buildStep("r1", {
383
+ url: CATALOG_PRICING_URL,
384
+ requestPostData: '{"sku":"sku-a"}',
385
+ responseBody: {
386
+ tags: [{ id: 1 }, { id: 2 }],
387
+ name: "Widget A",
388
+ price: 19.99,
389
+ brand: "Acme",
390
+ },
391
+ timestamp: "2024-04-01T00:00:01Z",
392
+ }),
393
+ ];
394
+ }
395
+ const CATALOG_INVENTORY_URL = "https://api.example.com/catalog/inventory/";
396
+ /**
397
+ * A decoy-free sibling of {@link buildMulticallSingleShotSearchDrillDownNoDecoyActionSteps}
398
+ * with a SECOND, independently-threaded per-item drill-down appended: the
399
+ * primary `results[]` items carry both a `sku` (joined to the pricing drill)
400
+ * and an `itemId` (joined to this inventory drill), and neither drill
401
+ * step's response overlaps the other's fields. Proves the fold loop merges
402
+ * fields from every independent target onto each item, not just the first.
403
+ */
404
+ function buildMulticallSingleShotSearchTwoIndependentDrillDownsActionSteps() {
405
+ return [
406
+ buildStep("r0", {
407
+ url: CATALOG_SEARCH_URL,
408
+ requestPostData: '{"page":1}',
409
+ responseBody: {
410
+ results: [
411
+ { sku: "sku-a", itemId: "item-a" },
412
+ { sku: "sku-b", itemId: "item-b" },
413
+ ],
414
+ },
415
+ timestamp: "2024-04-01T00:00:00Z",
416
+ }),
417
+ buildStep("r1", {
418
+ url: CATALOG_PRICING_URL,
419
+ requestPostData: '{"sku":"sku-a"}',
420
+ responseBody: { prices: [{ sku: "sku-a", amount: 19.99 }] },
421
+ timestamp: "2024-04-01T00:00:01Z",
422
+ }),
423
+ buildStep("r2", {
424
+ url: CATALOG_INVENTORY_URL,
425
+ requestPostData: '{"itemId":"item-a"}',
426
+ responseBody: { stock: [{ itemId: "item-a", qty: 7 }] },
427
+ timestamp: "2024-04-01T00:00:02Z",
428
+ }),
429
+ ];
430
+ }
431
+ /**
432
+ * A sibling of {@link buildMulticallSingleShotSearchTwoIndependentDrillDownsActionSteps}
433
+ * where BOTH per-item drills are threaded off the SAME primary join field
434
+ * (`sku`), rather than disjoint fields (`sku`/`itemId`). Each drill's
435
+ * response echoes that shared `sku` value back alongside its own field —
436
+ * an echo of the primary's own join value, not evidence the second drill
437
+ * depends on the first drill's response. Proves the fold loop still emits
438
+ * and merges BOTH independent drills onto every primary item even when
439
+ * their echoed join values collide, rather than collapsing the second
440
+ * drill into a chain under the first.
441
+ */
442
+ function buildMulticallSingleShotSearchTwoIndependentDrillDownsSharedJoinFieldActionSteps() {
443
+ return [
444
+ buildStep("r0", {
445
+ url: CATALOG_SEARCH_URL,
446
+ requestPostData: '{"page":1}',
447
+ responseBody: {
448
+ results: [{ sku: "sku-a" }, { sku: "sku-b" }],
449
+ },
450
+ timestamp: "2024-04-01T00:00:00Z",
451
+ }),
452
+ buildStep("r1", {
453
+ url: CATALOG_PRICING_URL,
454
+ requestPostData: '{"sku":"sku-a"}',
455
+ responseBody: { prices: [{ sku: "sku-a", amount: 19.99 }] },
456
+ timestamp: "2024-04-01T00:00:01Z",
457
+ }),
458
+ buildStep("r2", {
459
+ url: CATALOG_INVENTORY_URL,
460
+ requestPostData: '{"sku":"sku-a"}',
461
+ responseBody: { stock: [{ sku: "sku-a", qty: 7 }] },
462
+ timestamp: "2024-04-01T00:00:02Z",
463
+ }),
464
+ ];
465
+ }
466
+ const CATALOG_VENDORS_SEARCH_URL = "https://api.example.com/catalog/vendors/search";
467
+ const CATALOG_VENDOR_CONTRACTS_URL = "https://api.example.com/catalog/vendors/contracts/";
468
+ /**
469
+ * TWO independent primary/drill-down pairs, fully disjoint in both endpoint
470
+ * and step range: a products search folded by a per-product reviews drill
471
+ * (r0/r1), and an UNRELATED vendors search folded by a per-vendor contracts
472
+ * drill (r2/r3). Neither primary's items reference the other pair's join key
473
+ * (`productId` vs `vendorId`), so this proves the fold emitter resolves and
474
+ * loops over every independent plan, not only the first one it finds.
475
+ */
476
+ function buildMulticallTwoIndependentPrimariesActionSteps() {
477
+ return [
478
+ buildStep("r0", {
479
+ url: CATALOG_SEARCH_URL,
480
+ requestPostData: '{"page":1}',
481
+ responseBody: {
482
+ products: [{ productId: "p1" }, { productId: "p2" }],
483
+ },
484
+ timestamp: "2024-05-01T00:00:00Z",
485
+ }),
486
+ buildStep("r1", {
487
+ url: CATALOG_PRICING_URL,
488
+ requestPostData: '{"productId":"p1"}',
489
+ responseBody: { reviews: [{ productId: "p1", rating: 5 }] },
490
+ timestamp: "2024-05-01T00:00:01Z",
491
+ }),
492
+ buildStep("r2", {
493
+ url: CATALOG_VENDORS_SEARCH_URL,
494
+ requestPostData: '{"page":1}',
495
+ responseBody: {
496
+ vendors: [{ vendorId: "v1" }, { vendorId: "v2" }],
497
+ },
498
+ timestamp: "2024-05-01T00:00:02Z",
499
+ }),
500
+ buildStep("r3", {
501
+ url: CATALOG_VENDOR_CONTRACTS_URL,
502
+ requestPostData: '{"vendorId":"v1"}',
503
+ responseBody: { contracts: [{ vendorId: "v1", contractId: "c1" }] },
504
+ timestamp: "2024-05-01T00:00:03Z",
505
+ }),
506
+ ];
507
+ }
508
+ /**
509
+ * Same disjoint two-pair shape as
510
+ * {@link buildMulticallTwoIndependentPrimariesActionSteps}, except the SECOND
511
+ * pair's join value threads only through a request HEADER
512
+ * (`collectRequestStringValues` never scans headers), so
513
+ * `detectDrillDownFoldPlan`'s structural heuristic resolves only the FIRST
514
+ * pair (r0/r1) on its own; the second pair (r2/r3) is only reachable via a
515
+ * flow-declared `foldReturn` spec naming `vendors` as its `resultsPath`.
516
+ * Since the second pair's primary/drill steps (2, 3) are entirely outside the
517
+ * first pair's own consumed indices ({@link buildMulticallTwoIndependentPrimariesActionSteps}'s
518
+ * r0/r1), a correctly-behaving `resolveFoldPlan` must APPEND the spec's plan
519
+ * as a second, independent entry rather than discarding it just because its
520
+ * primary differs from the one the heuristic already resolved.
521
+ */
522
+ function buildMulticallTwoIndependentPrimariesSecondHeaderThreadedActionSteps() {
523
+ return [
524
+ buildStep("r0", {
525
+ url: CATALOG_SEARCH_URL,
526
+ requestPostData: '{"page":1}',
527
+ responseBody: {
528
+ products: [{ productId: "p1" }, { productId: "p2" }],
529
+ },
530
+ timestamp: "2024-05-01T00:00:00Z",
531
+ }),
532
+ buildStep("r1", {
533
+ url: CATALOG_PRICING_URL,
534
+ requestPostData: '{"productId":"p1"}',
535
+ responseBody: { reviews: [{ productId: "p1", rating: 5 }] },
536
+ timestamp: "2024-05-01T00:00:01Z",
537
+ }),
538
+ buildStep("r2", {
539
+ url: CATALOG_VENDORS_SEARCH_URL,
540
+ requestPostData: '{"page":1}',
541
+ responseBody: {
542
+ vendors: [{ vendorId: "v1" }, { vendorId: "v2" }],
543
+ },
544
+ timestamp: "2024-05-01T00:00:02Z",
545
+ }),
546
+ buildStep("r3", {
547
+ url: CATALOG_VENDOR_CONTRACTS_URL,
548
+ requestPostData: '{"lookup":true}',
549
+ responseBody: { contracts: [{ vendorId: "v1", contractId: "c1" }] },
550
+ timestamp: "2024-05-01T00:00:03Z",
551
+ requestHeaders: { "Content-Type": "application/json", "X-Vendor-Id": "v1" },
552
+ }),
553
+ ];
554
+ }
555
+ const CATALOG_STOCK_URL = "https://api.example.com/catalog/stock/";
556
+ /** A single-shot search drilled by two INDEPENDENT later calls whose join
557
+ * values thread through different surfaces: the pricing step's `sku` lands
558
+ * in its JSON body (heuristically detectable by `collectRequestStringValues`),
559
+ * while the stock step's `itemId` is carried ONLY in a request header —
560
+ * `collectRequestStringValues` never scans headers, so that fold can only be
561
+ * resolved via a flow-declared `foldReturn` spec. Every downstream unit and
562
+ * runtime test that needs one mixed-source, two-target fold scenario should
563
+ * share this fixture rather than inventing its own. */
564
+ function buildMulticallSingleShotSearchHeuristicAndSpecTwoTargetActionSteps() {
565
+ return [
566
+ buildStep("r0", {
567
+ url: CATALOG_SEARCH_URL,
568
+ requestPostData: '{"page":1}',
569
+ responseBody: {
570
+ results: [
571
+ { sku: "sku-a", itemId: "item-a" },
572
+ { sku: "sku-b", itemId: "item-b" },
573
+ ],
574
+ },
575
+ timestamp: "2024-04-01T00:00:00Z",
576
+ }),
577
+ buildStep("r1", {
578
+ url: CATALOG_PRICING_URL,
579
+ requestPostData: '{"sku":"sku-a"}',
580
+ responseBody: { prices: [{ sku: "sku-a", amount: 19.99 }] },
581
+ timestamp: "2024-04-01T00:00:01Z",
582
+ }),
583
+ buildStep("r2", {
584
+ url: CATALOG_STOCK_URL,
585
+ requestPostData: '{"lookup":true}',
586
+ responseBody: { stock: [{ itemId: "item-a", qty: 7 }] },
587
+ timestamp: "2024-04-01T00:00:02Z",
588
+ requestHeaders: { "Content-Type": "application/json", "X-Item-Id": "item-a" },
589
+ }),
590
+ ];
591
+ }
592
+ /**
593
+ * TWO occurrences of the SAME primary endpoint (same origin+pathname,
594
+ * differing only by query string), each independently drilling a
595
+ * DIFFERENT item into a DIFFERENT dependent call — unlike
596
+ * {@link buildMulticallSingleShotSearchDrillDownRequeriedPrimaryOverlapActionSteps}
597
+ * (whose two occurrences thread the SAME drill), these thread two distinct
598
+ * drills, so {@link detectDrillDownFoldPlan}'s freshest-wins collapse never
599
+ * applies and both occurrences resolve as their own independent
600
+ * {@link FoldPlan}. Both plans' primary responses share the SAME top-level
601
+ * array key (`results`) — the exact shape a plain `{ ...a, ...b }`
602
+ * object-spread silently collapses to the later plan's array alone, since
603
+ * the emitted return statement folds every resolved plan's own primary var
604
+ * together into the runtime response.
605
+ */
606
+ function buildMulticallTwoOccurrencesSamePrimaryDistinctDrillsActionSteps() {
607
+ return [
608
+ buildStep("r0", {
609
+ url: `${CATALOG_SEARCH_URL}?q=widgets`,
610
+ requestPostData: '{"page":1}',
611
+ responseBody: { results: [{ sku: "sku-a" }] },
612
+ timestamp: "2025-01-01T00:00:00Z",
613
+ }),
614
+ buildStep("r1", {
615
+ url: CATALOG_PRICING_URL,
616
+ requestPostData: '{"sku":"sku-a"}',
617
+ responseBody: { prices: [{ sku: "sku-a", amount: 19.99 }] },
618
+ timestamp: "2025-01-01T00:00:01Z",
619
+ }),
620
+ buildStep("r2", {
621
+ url: `${CATALOG_SEARCH_URL}?q=gadgets`,
622
+ requestPostData: '{"page":1}',
623
+ responseBody: { results: [{ sku: "sku-c" }] },
624
+ timestamp: "2025-01-01T00:00:02Z",
625
+ }),
626
+ buildStep("r3", {
627
+ url: CATALOG_INVENTORY_URL,
628
+ requestPostData: '{"sku":"sku-c"}',
629
+ responseBody: { stock: [{ sku: "sku-c", qty: 4 }] },
630
+ timestamp: "2025-01-01T00:00:03Z",
631
+ }),
632
+ ];
633
+ }
634
+ const CATALOG_SECTIONS_URL = "https://api.example.com/catalog/sections";
635
+ const CATALOG_ENTRY_DETAIL_URL = "https://api.example.com/catalog/entries/e2/details";
636
+ /**
637
+ * A grouped, nested-primary drill-down whose primary response carries TWO
638
+ * outer `sections[]` groups rather than the single group every other nested
639
+ * fixture in this file uses, with the drilled entry living in the SECOND
640
+ * group's `entries[]`, not the first. A fold that assumes the matched item
641
+ * always lives in group 0 (as a single-group fixture can never disprove)
642
+ * would resolve the fold onto `sections[0]` and silently attach the drill
643
+ * response's `description` to the wrong entry.
644
+ */
645
+ function buildMulticallNestedGroupedDrillDownMultiGroupActionSteps() {
646
+ return [
647
+ buildStep("r0", {
648
+ url: CATALOG_SECTIONS_URL,
649
+ requestPostData: null,
650
+ responseBody: {
651
+ sections: [
652
+ {
653
+ label: "featured",
654
+ entries: [
655
+ { entryId: "e1", name: "Widget" },
656
+ { entryId: "e3", name: "Doohickey" },
657
+ ],
658
+ },
659
+ {
660
+ label: "clearance",
661
+ entries: [
662
+ { entryId: "e2", name: "Gadget" },
663
+ { entryId: "e4", name: "Thingamajig" },
664
+ ],
665
+ },
666
+ ],
667
+ },
668
+ timestamp: "2024-11-01T00:00:00Z",
669
+ }),
670
+ buildStep("r1", {
671
+ url: CATALOG_ENTRY_DETAIL_URL,
672
+ requestPostData: null,
673
+ responseBody: { details: [{ entryId: "e2", description: "A gadget." }] },
674
+ timestamp: "2024-11-01T00:00:01Z",
675
+ }),
676
+ ];
677
+ }
678
+ const ACCOUNT_SEARCH_URL = "https://api.example.com/accounts/search";
679
+ const ACCOUNT_DETAIL_URL = "https://api.example.com/accounts/detail";
680
+ /**
681
+ * A single-shot search whose primary item join field (`accountId`) is a
682
+ * NUMBER rather than a string, threaded into the drill-down call via a URL
683
+ * query parameter (`?accountId=42`). `URLSearchParams` always stringifies its
684
+ * values, so the request-collection side already captures `"42"` as a
685
+ * string; the item side of the join is what must widen to accept a numeric
686
+ * leaf for {@link detectDrillDownFoldPlan} to resolve `accountId` as a join
687
+ * field at all.
688
+ */
689
+ function buildMulticallSingleShotSearchDrillDownNumericJoinActionSteps() {
690
+ return [
691
+ buildStep("r0", {
692
+ url: ACCOUNT_SEARCH_URL,
693
+ requestPostData: JSON.stringify({ page: 1 }),
694
+ responseBody: { accounts: [{ accountId: 42, name: "Acme" }] },
695
+ timestamp: "2024-05-01T00:00:00Z",
696
+ }),
697
+ buildStep("r1", {
698
+ url: `${ACCOUNT_DETAIL_URL}?accountId=42`,
699
+ requestPostData: null,
700
+ responseBody: { transactions: [{ transactionId: "t1" }] },
701
+ timestamp: "2024-05-01T00:00:01Z",
702
+ }),
703
+ ];
704
+ }
705
+ /**
706
+ * A single-shot search whose primary item join field (`accountId`) is
707
+ * threaded into the drill-down call ONLY as a URL path segment
708
+ * (`/accounts/42/transactions`) — never as a query param or a JSON body
709
+ * value. REST-style APIs commonly shape drill-down calls this way, but
710
+ * `collectRequestStringValues` only harvested query params and JSON body
711
+ * leaves, so this join field was invisible to `findThreadedJoinFields` and
712
+ * {@link detectDrillDownFoldPlan} returned null for this shape.
713
+ */
714
+ function buildMulticallSingleShotSearchDrillDownPathThreadedJoinActionSteps() {
715
+ return [
716
+ buildStep("r0", {
717
+ url: ACCOUNT_SEARCH_URL,
718
+ requestPostData: JSON.stringify({ page: 1 }),
719
+ responseBody: { accounts: [{ accountId: 42, name: "Acme" }] },
720
+ timestamp: "2024-07-01T00:00:00Z",
721
+ }),
722
+ buildStep("r1", {
723
+ url: "https://api.example.com/accounts/42/transactions",
724
+ requestPostData: null,
725
+ responseBody: { transactions: [{ transactionId: "t1" }] },
726
+ timestamp: "2024-07-01T00:00:01Z",
727
+ }),
728
+ ];
729
+ }
730
+ /**
731
+ * A single-shot search whose primary item join field (`sku`) sits inside a
732
+ * NESTED object (`{ identifiers: { sku } }`) rather than as a top-level
733
+ * property. `findThreadedJoinFields` must walk into nested plain objects to
734
+ * find it, and the resulting joinFields entry is the dot-separated path
735
+ * `"identifiers.sku"`, not the bare leaf name `"sku"`.
736
+ */
737
+ function buildMulticallSingleShotSearchDrillDownNestedJoinFieldActionSteps() {
738
+ return [
739
+ buildStep("r0", {
740
+ url: ACCOUNT_SEARCH_URL,
741
+ requestPostData: JSON.stringify({ page: 1 }),
742
+ responseBody: { accounts: [{ identifiers: { sku: "SKU-1" }, name: "Acme" }] },
743
+ timestamp: "2024-08-01T00:00:00Z",
744
+ }),
745
+ buildStep("r1", {
746
+ url: ACCOUNT_DETAIL_URL,
747
+ requestPostData: JSON.stringify({ sku: "SKU-1" }),
748
+ responseBody: { transactions: [{ transactionId: "t1" }] },
749
+ timestamp: "2024-08-01T00:00:01Z",
750
+ }),
751
+ ];
752
+ }
753
+ /**
754
+ * A single-shot search whose primary items carry a COMPOSITE join key mixing
755
+ * a string field (`region`) and a numeric field (`accountId`), both threaded
756
+ * into the drill-down request. `findThreadedJoinFields` filters candidate
757
+ * fields independently per entry, so a numeric field dropped alongside a
758
+ * present string field would silently degrade the fold to a partial
759
+ * (string-only) join key rather than failing outright — this fixture proves
760
+ * both survive together, in the item's own key order.
761
+ */
762
+ function buildMulticallSingleShotSearchDrillDownCompositeNumericJoinActionSteps() {
763
+ return [
764
+ buildStep("r0", {
765
+ url: ACCOUNT_SEARCH_URL,
766
+ requestPostData: JSON.stringify({ page: 1 }),
767
+ responseBody: { accounts: [{ region: "us", accountId: 7, name: "Acme" }] },
768
+ timestamp: "2024-06-01T00:00:00Z",
769
+ }),
770
+ buildStep("r1", {
771
+ url: `${ACCOUNT_DETAIL_URL}?region=us&accountId=7`,
772
+ requestPostData: null,
773
+ responseBody: { transactions: [{ transactionId: "t1" }] },
774
+ timestamp: "2024-06-01T00:00:01Z",
775
+ }),
776
+ ];
777
+ }
778
+ /**
779
+ * A decoy-free single-shot search sibling of
780
+ * {@link buildMulticallSingleShotSearchDrillDownNoDecoyActionSteps} whose
781
+ * `results[]` has TWO items, but whose only captured drill-down call was
782
+ * made for the SECOND item (`sku-b`), not the first. A heuristic that always
783
+ * pairs the primary's `results[0]` with the sole drill step would thread
784
+ * `sku-a` into the fold even though the captured request body only ever
785
+ * mentions `sku-b` — proving the fold must select the drill call's item by
786
+ * matching the join field's actual value, not by array position.
787
+ */
788
+ function buildMulticallSingleShotSearchDrillDownNonFirstItemSkuActionSteps() {
789
+ return [
790
+ buildStep("r0", {
791
+ url: CATALOG_SEARCH_URL,
792
+ requestPostData: '{"page":1}',
793
+ responseBody: {
794
+ results: [{ sku: "sku-a" }, { sku: "sku-b" }],
795
+ },
796
+ timestamp: "2024-09-01T00:00:00Z",
797
+ }),
798
+ buildStep("r1", {
799
+ url: CATALOG_PRICING_URL,
800
+ requestPostData: '{"sku":"sku-b"}',
801
+ responseBody: { prices: [{ sku: "sku-b", amount: 24.99 }] },
802
+ timestamp: "2024-09-01T00:00:01Z",
803
+ }),
804
+ ];
805
+ }
806
+ /**
807
+ * A composite-join sibling of
808
+ * {@link buildMulticallSingleShotSearchDrillDownCompositeNumericJoinActionSteps}
809
+ * whose `accounts[]` has TWO items, but whose only captured drill-down call
810
+ * threads the SECOND item's composite join key (`region: "eu"`,
811
+ * `accountId: 9`), never the first item's (`region: "us"`, `accountId: 7`).
812
+ * Proves the same non-first-item selection requirement holds when the join
813
+ * key is composite, not just when it's a single field.
814
+ */
815
+ function buildMulticallSingleShotSearchDrillDownCompositeNumericJoinNonFirstItemActionSteps() {
816
+ return [
817
+ buildStep("r0", {
818
+ url: ACCOUNT_SEARCH_URL,
819
+ requestPostData: JSON.stringify({ page: 1 }),
820
+ responseBody: {
821
+ accounts: [
822
+ { region: "us", accountId: 7, name: "Acme" },
823
+ { region: "eu", accountId: 9, name: "Globex" },
824
+ ],
825
+ },
826
+ timestamp: "2024-09-01T00:00:00Z",
827
+ }),
828
+ buildStep("r1", {
829
+ url: `${ACCOUNT_DETAIL_URL}?region=eu&accountId=9`,
830
+ requestPostData: null,
831
+ responseBody: { transactions: [{ transactionId: "t2" }] },
832
+ timestamp: "2024-09-01T00:00:01Z",
833
+ }),
834
+ ];
835
+ }
836
+ /**
837
+ * A single-shot search whose primary item join field (`accountId`) is
838
+ * threaded into the drill-down call ONLY via a custom request header
839
+ * (`X-Account-Id`) — never a query param, JSON body value, or URL path
840
+ * segment. `collectRequestStringValues` deliberately never scans
841
+ * `requestHeaders` (recon-generate.ts:4787-4815), so this join is invisible
842
+ * to `findThreadedJoinFields` and {@link detectDrillDownFoldPlan} must return
843
+ * `null` here regardless of how many other threading channels it learns to
844
+ * scan — only a flow-declared `foldReturn` naming `accountId` can resolve
845
+ * this fold.
846
+ */
847
+ function buildMulticallSingleShotSearchDrillDownHeaderThreadedJoinActionSteps() {
848
+ return [
849
+ buildStep("r0", {
850
+ url: ACCOUNT_SEARCH_URL,
851
+ requestPostData: JSON.stringify({ page: 1 }),
852
+ responseBody: { accounts: [{ accountId: 42, name: "Acme" }] },
853
+ timestamp: "2024-08-01T00:00:00Z",
854
+ }),
855
+ buildStep("r1", {
856
+ url: ACCOUNT_DETAIL_URL,
857
+ requestPostData: null,
858
+ requestHeaders: { "Content-Type": "application/json", "X-Account-Id": "42" },
859
+ responseBody: { transactions: [{ transactionId: "t1" }] },
860
+ timestamp: "2024-08-01T00:00:01Z",
861
+ }),
862
+ ];
863
+ }
864
+ /**
865
+ * A sibling of {@link buildMulticallSingleShotSearchDrillDownHeaderThreadedJoinActionSteps}
866
+ * whose primary endpoint is hit TWICE (`r0`, `r1`), each occurrence's single
867
+ * `accounts[]` item independently satisfying the drill-down's `X-Account-Id`
868
+ * join on its own — mirroring
869
+ * {@link buildMulticallSingleShotSearchDrillDownRequeriedPrimaryOverlapActionSteps}'s
870
+ * requeried-primary-overlap shape, but with the join threaded ONLY through a
871
+ * request header rather than a URL query param, so `detectDrillDownFoldPlan`'s
872
+ * structural heuristic can never see it (same blind spot documented on
873
+ * {@link buildMulticallSingleShotSearchDrillDownHeaderThreadedJoinActionSteps}).
874
+ * Only a flow-declared `foldReturn` naming `accountId` can resolve this fold,
875
+ * isolating `buildFoldPlanFromSpec`'s own freshest-wins primary lookahead:
876
+ * the resolved plan must anchor on `r1` (the LATER occurrence) and its
877
+ * differing `name` value ("Acme Corp"), not `r0`'s stale "Acme".
878
+ */
879
+ function buildMulticallSingleShotSearchDrillDownHeaderThreadedJoinRequeriedPrimaryOverlapActionSteps() {
880
+ return [
881
+ buildStep("r0", {
882
+ url: ACCOUNT_SEARCH_URL,
883
+ requestPostData: JSON.stringify({ page: 1 }),
884
+ responseBody: { accounts: [{ accountId: 42, name: "Acme" }] },
885
+ timestamp: "2024-08-01T00:00:00Z",
886
+ }),
887
+ buildStep("r1", {
888
+ url: ACCOUNT_SEARCH_URL,
889
+ requestPostData: JSON.stringify({ page: 2 }),
890
+ responseBody: { accounts: [{ accountId: 42, name: "Acme Corp" }] },
891
+ timestamp: "2024-08-01T00:00:01Z",
892
+ }),
893
+ buildStep("r2", {
894
+ url: ACCOUNT_DETAIL_URL,
895
+ requestPostData: null,
896
+ requestHeaders: { "Content-Type": "application/json", "X-Account-Id": "42" },
897
+ responseBody: { transactions: [{ transactionId: "t1" }] },
898
+ timestamp: "2024-08-01T00:00:02Z",
899
+ }),
900
+ ];
901
+ }
902
+ /**
903
+ * Sibling of {@link buildMulticallSingleShotSearchDrillDownHeaderThreadedJoinActionSteps}
904
+ * whose primary `accounts[]` array holds TWO items rather than one, so a
905
+ * runtime fold loop built from this capture must re-key the drill-down's
906
+ * request header per iteration rather than replaying the single captured
907
+ * header value.
908
+ *
909
+ * Threaded on `API-Token` rather than the sibling fixture's `X-Account-Id`:
910
+ * `emitMultiStepExecuteHttp`'s per-call header emission only ever renders a
911
+ * captured header back into the generated request when it recognizes the
912
+ * header name (`API-Token`/`Authorization`, or a base-URL-/tenant-derived
913
+ * header passed in separately — recon-generate.ts:4273-4279) — an arbitrary
914
+ * custom header like `X-Account-Id` is captured but never re-emitted, so a
915
+ * fold loop built from it would have nothing to re-key at runtime. `API-Token`
916
+ * is still a request header the join value reaches through NO OTHER channel
917
+ * (not the URL, which is identical on every call, and not the body, which is
918
+ * empty), so it still exercises the same header-threaded-join runtime path
919
+ * {@link buildMulticallSingleShotSearchDrillDownHeaderThreadedJoinActionSteps}
920
+ * demonstrates is invisible to the structural heuristic.
921
+ *
922
+ * The captured drill call was made for `accountId: 43` (the SECOND item),
923
+ * pinning `primaryMatchedItemIndex` away from the index-0 default.
924
+ */
925
+ function buildMulticallSingleShotSearchDrillDownHeaderThreadedJoinMultiItemActionSteps() {
926
+ return [
927
+ buildStep("r0", {
928
+ url: ACCOUNT_SEARCH_URL,
929
+ requestPostData: JSON.stringify({ page: 1 }),
930
+ responseBody: {
931
+ accounts: [
932
+ { accountId: 42, name: "Acme" },
933
+ { accountId: 43, name: "Globex" },
934
+ ],
935
+ },
936
+ timestamp: "2024-08-01T00:00:00Z",
937
+ }),
938
+ buildStep("r1", {
939
+ url: ACCOUNT_DETAIL_URL,
940
+ requestPostData: null,
941
+ requestHeaders: { "Content-Type": "application/json", "API-Token": "43" },
942
+ responseBody: { transactions: [{ transactionId: "t2" }] },
943
+ timestamp: "2024-08-01T00:00:01Z",
944
+ }),
945
+ ];
946
+ }
947
+ /**
948
+ * A single-shot search whose primary results array is NOT drilled into at
949
+ * item 0 — only a single later drill call exists, and it threads the second
950
+ * item's (`itemId: "i-a"`) join value, never the first's (`itemId: "i-b"`).
951
+ * With no coincidental extra call re-drilling item 0 (unlike
952
+ * {@link buildMulticallDependentDrillDownActionSteps}, whose items are also
953
+ * out of order but which happens to pass a items[0]-only match because a
954
+ * third step re-drills item 0), this pins {@link detectDrillDownFoldPlan}'s
955
+ * item search to the actually-drilled item rather than an index-0 default —
956
+ * the search must find the match at `primaryMatchedItemIndex` 1.
957
+ * Named "OutOfOrder" (rather than "NonFirstItem") to avoid colliding with
958
+ * {@link buildMulticallSingleShotSearchDrillDownNonFirstItemSkuActionSteps}.
959
+ */
960
+ function buildMulticallSingleShotSearchDrillDownOutOfOrderItemActionSteps() {
961
+ return [
962
+ buildStep("r0", {
963
+ url: CATALOG_SEARCH_URL,
964
+ requestPostData: '{"page":1}',
965
+ responseBody: {
966
+ results: [{ itemId: "i-b" }, { itemId: "i-a" }],
967
+ },
968
+ timestamp: "2024-09-01T00:00:00Z",
969
+ }),
970
+ buildStep("r1", {
971
+ url: CATALOG_PRICING_URL,
972
+ requestPostData: '{"itemId":"i-a"}',
973
+ responseBody: { prices: [{ itemId: "i-a", amount: 19.99 }] },
974
+ timestamp: "2024-09-01T00:00:01Z",
975
+ }),
976
+ ];
977
+ }
978
+ /**
979
+ * A single-shot search sibling of
980
+ * {@link buildMulticallSingleShotSearchDrillDownNonFirstItemSkuActionSteps}
981
+ * whose axis is on the DRILL side rather than the primary side: the primary
982
+ * `results[]` has a single item (`sku-a`), but the drill-down's own
983
+ * `prices[]` array comes back holding TWO entries — a decoy for an unrelated
984
+ * sku (`sku-z`, listed first) alongside the real match (`sku-a`, listed
985
+ * second). A fold that merges `foldMatches[0]`/`drillItems?.[0]` would
986
+ * silently splice the decoy's `amount` onto the primary item; only matching
987
+ * the drill array's own entries against the threaded join field (`sku`)
988
+ * picks the correct one, at a non-zero index within that array.
989
+ */
990
+ function buildMulticallSingleShotSearchDrillDownMultiMatchActionSteps() {
991
+ return [
992
+ buildStep("r0", {
993
+ url: CATALOG_SEARCH_URL,
994
+ requestPostData: '{"page":1}',
995
+ responseBody: {
996
+ results: [{ sku: "sku-a" }],
997
+ },
998
+ timestamp: "2024-09-01T00:00:00Z",
999
+ }),
1000
+ buildStep("r1", {
1001
+ url: CATALOG_PRICING_URL,
1002
+ requestPostData: '{"sku":"sku-a"}',
1003
+ responseBody: {
1004
+ prices: [
1005
+ { sku: "sku-z", amount: 5.0 },
1006
+ { sku: "sku-a", amount: 19.99 },
1007
+ ],
1008
+ },
1009
+ timestamp: "2024-09-01T00:00:01Z",
1010
+ }),
1011
+ ];
1012
+ }
1013
+ /**
1014
+ * A multi-match sibling of
1015
+ * {@link buildMulticallSingleShotSearchDrillDownMultiMatchActionSteps} whose
1016
+ * join field's type DIFFERS across the two sides: the primary item's
1017
+ * `accountId` is a NUMBER, while every item in the drill-down response's
1018
+ * array carries `accountId` as a STRING (as request-threaded values always
1019
+ * are). A decoy entry is listed first with a string that never equals the
1020
+ * primary's numeric value even after coercion; only the second entry's
1021
+ * string stringifies to the primary's value. A fold that matches via strict
1022
+ * equality (`m["accountId"] === item.accountId`, i.e. `"42" === 42`) fails
1023
+ * for every candidate here, proving the fold must compare join values by
1024
+ * their string representation, not by strict identity.
1025
+ */
1026
+ function buildMulticallSingleShotSearchDrillDownTypeMismatchJoinActionSteps() {
1027
+ return [
1028
+ buildStep("r0", {
1029
+ url: ACCOUNT_SEARCH_URL,
1030
+ requestPostData: JSON.stringify({ page: 1 }),
1031
+ responseBody: { accounts: [{ accountId: 42, name: "Acme" }] },
1032
+ timestamp: "2024-10-01T00:00:00Z",
1033
+ }),
1034
+ buildStep("r1", {
1035
+ url: `${ACCOUNT_DETAIL_URL}?accountId=42`,
1036
+ requestPostData: null,
1037
+ responseBody: {
1038
+ transactions: [
1039
+ { accountId: "99", transactionId: "decoy" },
1040
+ { accountId: "42", transactionId: "t-real" },
1041
+ ],
1042
+ },
1043
+ timestamp: "2024-10-01T00:00:01Z",
1044
+ }),
1045
+ ];
1046
+ }
1047
+ /**
1048
+ * Same shape as {@link buildMulticallSingleShotSearchDrillDownChainedDependentActionSteps},
1049
+ * but the CHAIN's second step (the price-history call `r2`, not the drill
1050
+ * step `r1` itself) is a multipart upload rather than a JSON POST.
1051
+ * `emitMultiStepExecuteHttp`'s fold loop re-issues EVERY chain step's
1052
+ * request per item by re-keying its rendered JSON body template — a
1053
+ * multipart step anywhere in `chain`, not just at `drillStepIndex`, has no
1054
+ * such template to re-key, so `resolveFoldPlan` must disqualify the whole
1055
+ * plan here exactly as it already does when the drill step itself is
1056
+ * multipart.
1057
+ */
1058
+ function buildMulticallSingleShotSearchDrillDownChainedDependentMultipartChainStepActionSteps() {
1059
+ const steps = buildMulticallSingleShotSearchDrillDownChainedDependentActionSteps();
1060
+ return steps.map((step, index) => (index === 2 ? { ...step, isMultipart: true } : step));
1061
+ }
1062
+ const CATALOG_PRICE_HISTORY_URL = "https://api.example.com/catalog/price-history";
1063
+ /**
1064
+ * A single-shot search → per-item drill-down flow whose drill step's OWN
1065
+ * response is foldable on its own terms — it carries `prices[]`, the same
1066
+ * per-item results shape {@link buildMulticallSingleShotSearchDrillDownNoDecoyActionSteps}
1067
+ * would fold directly — but is ALSO depended on by a THIRD step: the drill
1068
+ * response's `priceToken` threads into a price-history call whose response
1069
+ * carries the real per-item array this flow means to fold. `computeFoldChain`
1070
+ * must extend the plan's chain to `[drillStepIndex, historyStepIndex]`
1071
+ * instead of stopping at the drill step's own array, so
1072
+ * `emitMultiStepExecuteHttp`'s fold loop renders BOTH calls per item and
1073
+ * shape inference ({@link foldResponseBodyForShapeInference}) folds the
1074
+ * chain's TERMINAL (history) response, not the drill step's own `prices[]`.
1075
+ */
1076
+ function buildMulticallSingleShotSearchDrillDownChainedDependentActionSteps() {
1077
+ return [
1078
+ buildStep("r0", {
1079
+ url: CATALOG_SEARCH_URL,
1080
+ requestPostData: '{"page":1}',
1081
+ responseBody: {
1082
+ results: [{ sku: "sku-a" }, { sku: "sku-b" }],
1083
+ },
1084
+ timestamp: "2024-11-15T00:00:00Z",
1085
+ }),
1086
+ buildStep("r1", {
1087
+ url: CATALOG_PRICING_URL,
1088
+ requestPostData: '{"sku":"sku-a"}',
1089
+ responseBody: {
1090
+ priceToken: "tok-a1",
1091
+ prices: [{ sku: "sku-a", amount: 19.99 }],
1092
+ },
1093
+ timestamp: "2024-11-15T00:00:01Z",
1094
+ }),
1095
+ buildStep("r2", {
1096
+ url: CATALOG_PRICE_HISTORY_URL,
1097
+ requestPostData: '{"priceToken":"tok-a1"}',
1098
+ responseBody: {
1099
+ history: [{ sku: "sku-a", amount: 18.5, asOf: "2024-11-01" }],
1100
+ },
1101
+ timestamp: "2024-11-15T00:00:02Z",
1102
+ }),
1103
+ ];
1104
+ }
1105
+ /**
1106
+ * A nested-join-key sibling of {@link buildMulticallSingleShotSearchDrillDownChainedDependentActionSteps}:
1107
+ * each primary `results[]` item carries its join key under a nested
1108
+ * `identifiers` object (`{ identifiers: { sku } }`, matching
1109
+ * {@link buildMulticallSingleShotSearchDrillDownNestedJoinFieldMultiItemActionSteps})
1110
+ * rather than as a top-level field, AND (as in the chained-dependent fixture)
1111
+ * the drill step's own response is foldable on its own terms but is ALSO
1112
+ * depended on by a further step whose response carries the real per-item
1113
+ * data. The primary item that threads the nested join is the SECOND item
1114
+ * (`sku-b`), not the first, so `primaryMatchedItemIndex` must resolve by
1115
+ * walking into each item's nested fields rather than assuming index 0, while
1116
+ * `computeFoldChain` must still extend the chain past the drill step to its
1117
+ * dependent follow-up rather than stopping at the drill step's own array.
1118
+ */
1119
+ function buildMulticallSingleShotSearchDrillDownNestedJoinFieldChainedDependentActionSteps() {
1120
+ return [
1121
+ buildStep("r0", {
1122
+ url: CATALOG_SEARCH_URL,
1123
+ requestPostData: '{"page":1}',
1124
+ responseBody: {
1125
+ results: [{ identifiers: { sku: "sku-a" } }, { identifiers: { sku: "sku-b" } }],
1126
+ },
1127
+ timestamp: "2024-11-20T00:00:00Z",
1128
+ }),
1129
+ buildStep("r1", {
1130
+ url: CATALOG_PRICING_URL,
1131
+ requestPostData: '{"sku":"sku-b"}',
1132
+ responseBody: {
1133
+ priceToken: "tok-b1",
1134
+ prices: [{ sku: "sku-b", amount: 24.99 }],
1135
+ },
1136
+ timestamp: "2024-11-20T00:00:01Z",
1137
+ }),
1138
+ buildStep("r2", {
1139
+ url: CATALOG_PRICE_HISTORY_URL,
1140
+ requestPostData: '{"priceToken":"tok-b1"}',
1141
+ responseBody: {
1142
+ history: [{ sku: "sku-b", amount: 22.5, asOf: "2024-11-01" }],
1143
+ },
1144
+ timestamp: "2024-11-20T00:00:02Z",
1145
+ }),
1146
+ ];
1147
+ }
1148
+ /**
1149
+ * A search endpoint re-queried with two distinct bodies (`page 1`/`page 2`,
1150
+ * satisfying {@link findRequeriedActions}'s re-query signature) whose
1151
+ * responses BOTH independently contain a `results[]` item sharing the same
1152
+ * `sku` — but at a different `price` (10 vs 12) — followed by a pricing
1153
+ * drill-down call threading that `sku` via its URL query param. Every prior
1154
+ * action is a fold-primary candidate for {@link detectDrillDownFoldPlan}, so
1155
+ * both step 0 and step 1 satisfy the drill's join; the primary-candidate
1156
+ * scan currently commits to the FIRST match (step 0, `price: 10`) and
1157
+ * globally consumes the drill step, so step 1 — the fresher, `price: 12`
1158
+ * re-queried occurrence — is never considered. `amount` in the drill
1159
+ * response is the object-array field distinct from `price` that a fold
1160
+ * assertion reads to observe which occurrence was actually folded onto.
1161
+ */
1162
+ function buildMulticallSingleShotSearchDrillDownRequeriedPrimaryOverlapActionSteps() {
1163
+ return [
1164
+ buildStep("r0", {
1165
+ url: CATALOG_SEARCH_URL,
1166
+ requestPostData: '{"page":1}',
1167
+ responseBody: {
1168
+ results: [{ sku: "sku-a", price: 10 }],
1169
+ },
1170
+ timestamp: "2024-12-01T00:00:00Z",
1171
+ }),
1172
+ buildStep("r1", {
1173
+ url: CATALOG_SEARCH_URL,
1174
+ requestPostData: '{"page":2}',
1175
+ responseBody: {
1176
+ results: [{ sku: "sku-a", price: 12 }],
1177
+ },
1178
+ timestamp: "2024-12-01T00:00:01Z",
1179
+ }),
1180
+ buildStep("r2", {
1181
+ url: `${CATALOG_PRICING_URL}?sku=sku-a`,
1182
+ requestPostData: null,
1183
+ responseBody: {
1184
+ prices: [{ sku: "sku-a", amount: 19.99 }],
1185
+ },
1186
+ timestamp: "2024-12-01T00:00:02Z",
1187
+ }),
1188
+ ];
1189
+ }
1190
+ const CATALOG_VENDOR_DETAIL_URL = "https://api.example.com/catalog/vendors/detail/";
1191
+ /**
1192
+ * A single primary search response carrying TWO genuinely independent
1193
+ * object-array fields — `products[]` and `vendors[]` — each drilled by its
1194
+ * own, unrelated later step (`sku` into a pricing lookup, `vendorId` into a
1195
+ * vendor-detail lookup). `scanPrimaryCandidate`'s old candidatePool lock
1196
+ * (recon-generate.ts:5411-5413) resolved only whichever array its FIRST
1197
+ * qualifying drill-down threaded from (`products[]`, via r1) and then
1198
+ * restricted every later candidate scan to that SAME array, so r2's
1199
+ * `vendorId` thread into `vendors[]` was silently discarded — this fixture
1200
+ * proves BOTH arrays fold independently, one FoldPlan per distinct
1201
+ * `primaryArrayPath`.
1202
+ */
1203
+ function buildMulticallSingleShotSearchTwoIndependentArraysActionSteps() {
1204
+ return [
1205
+ buildStep("r0", {
1206
+ url: CATALOG_SEARCH_URL,
1207
+ requestPostData: '{"page":1}',
1208
+ responseBody: {
1209
+ products: [{ sku: "sku-a" }],
1210
+ vendors: [{ vendorId: "v1" }],
1211
+ },
1212
+ timestamp: "2025-01-01T00:00:00Z",
1213
+ }),
1214
+ buildStep("r1", {
1215
+ url: `${CATALOG_PRICING_URL}?sku=sku-a`,
1216
+ requestPostData: null,
1217
+ responseBody: {
1218
+ prices: [{ sku: "sku-a", amount: 9.99 }],
1219
+ },
1220
+ timestamp: "2025-01-01T00:00:01Z",
1221
+ }),
1222
+ buildStep("r2", {
1223
+ url: `${CATALOG_VENDOR_DETAIL_URL}?vendorId=v1`,
1224
+ requestPostData: null,
1225
+ responseBody: {
1226
+ contracts: [{ vendorId: "v1", contractId: "c1" }],
1227
+ },
1228
+ timestamp: "2025-01-01T00:00:02Z",
1229
+ }),
1230
+ ];
1231
+ }
1232
+ /**
1233
+ * A single primary search response carrying TWO independent object-array
1234
+ * fields — `products[]`, whose `sku` join threads through the pricing call's
1235
+ * own JSON body and so is structurally detectable, and `vendors[]`, whose
1236
+ * `vendorId` join threads ONLY through the vendor-detail call's
1237
+ * `X-Vendor-Id` request header, invisible to `collectRequestStringValues`
1238
+ * and resolvable only via a flow-declared `foldReturn` spec naming `vendors`
1239
+ * as its `resultsPath`. `mergeSpecPlanOntoSamePrimary` used to key its
1240
+ * consumed-index check off `primaryStepIndex` alone, so the spec's plan —
1241
+ * anchored on the SAME step 0 the structural heuristic had already resolved,
1242
+ * but naming a DIFFERENT array — was wrongly treated as already consumed and
1243
+ * silently dropped instead of appended.
1244
+ */
1245
+ function buildMulticallStructuralPlusSpecOnlySameStepActionSteps() {
1246
+ return [
1247
+ buildStep("r0", {
1248
+ url: CATALOG_SEARCH_URL,
1249
+ requestPostData: '{"page":1}',
1250
+ responseBody: {
1251
+ products: [{ sku: "sku-a" }],
1252
+ vendors: [{ vendorId: "v1" }],
1253
+ },
1254
+ timestamp: "2025-03-01T00:00:00Z",
1255
+ }),
1256
+ buildStep("r1", {
1257
+ url: `${CATALOG_PRICING_URL}?sku=sku-a`,
1258
+ requestPostData: null,
1259
+ responseBody: {
1260
+ prices: [{ sku: "sku-a", amount: 9.99 }],
1261
+ },
1262
+ timestamp: "2025-03-01T00:00:01Z",
1263
+ }),
1264
+ buildStep("r2", {
1265
+ url: CATALOG_VENDOR_DETAIL_URL,
1266
+ requestPostData: '{"lookup":true}',
1267
+ responseBody: {
1268
+ contracts: [{ vendorId: "v1", contractId: "c1" }],
1269
+ },
1270
+ timestamp: "2025-03-01T00:00:02Z",
1271
+ requestHeaders: { "Content-Type": "application/json", "X-Vendor-Id": "v1" },
1272
+ }),
1273
+ ];
1274
+ }
1275
+ const ORDER_HISTORY_URL = "https://api.example.com/orders/history/";
1276
+ /**
1277
+ * A 3-step chain where the drill step (`r1`) is a bare threading step — its
1278
+ * response carries the join value (`statusToken`) but no object-array field
1279
+ * at all, so it can't itself be the decoy. The decoy instead sits on the
1280
+ * chain's terminal step (`r2`): a `warnings[]` array positioned BEFORE the
1281
+ * real per-item `entries[]` array in key order, where `entries[]`'s items
1282
+ * thread `r2`'s own `statusToken` request value and `warnings[]`'s items do
1283
+ * not. {@link computeFoldChain}'s `selectDisambiguatedCandidate` call on
1284
+ * this (non-immediate) chain hop must resolve `chainArrayPath` to `entries`
1285
+ * on the threaded-join-fields match, not `warnings` for being found first.
1286
+ */
1287
+ function buildMulticallSingleShotSearchDrillDownChainedDecoyOnChainTerminalActionSteps() {
1288
+ return [
1289
+ buildStep("r0", {
1290
+ url: CATALOG_SEARCH_URL,
1291
+ requestPostData: '{"page":1}',
1292
+ responseBody: {
1293
+ results: [{ orderId: "order-7" }],
1294
+ },
1295
+ timestamp: "2024-09-01T00:00:00Z",
1296
+ }),
1297
+ buildStep("r1", {
1298
+ url: "https://api.example.com/orders/order-7/status",
1299
+ requestPostData: null,
1300
+ responseBody: {
1301
+ statusToken: "tok-99",
1302
+ },
1303
+ timestamp: "2024-09-01T00:00:01Z",
1304
+ }),
1305
+ buildStep("r2", {
1306
+ url: ORDER_HISTORY_URL,
1307
+ requestPostData: '{"statusToken":"tok-99"}',
1308
+ responseBody: {
1309
+ warnings: [{ code: "stale-cache" }],
1310
+ entries: [{ statusToken: "tok-99", ts: "2024-09-01T00:00:02Z", event: "shipped" }],
1311
+ },
1312
+ timestamp: "2024-09-01T00:00:02Z",
1313
+ }),
1314
+ ];
1315
+ }
1316
+ const CATALOG_ORDER_STATUS_URL = "https://api.example.com/catalog/order-status";
1317
+ /**
1318
+ * A single-shot search → per-item drill-down flow where the drill step's
1319
+ * OWN response (`r1`) is entirely OPAQUE — a bare array holding a single
1320
+ * token, satisfying neither `isObjectArrayItem` nor
1321
+ * `findAllObjectArrayFields`, so `selectDisambiguatedCandidate` finds no
1322
+ * candidate there at all — and is depended on by a THIRD step (`r2`) whose
1323
+ * response carries the real per-item array this flow means to fold. Unlike
1324
+ * {@link buildMulticallSingleShotSearchDrillDownChainedDependentActionSteps},
1325
+ * whose `r1` is ALSO independently foldable, this fixture isolates the case
1326
+ * where `computeFoldChain` has nothing to fall back on at the immediate hop
1327
+ * and must extend the chain past it to `r2` to find any candidate at all.
1328
+ */
1329
+ function buildMulticallSingleShotSearchDrillDownOpaqueIntermediateChainedDependentActionSteps() {
1330
+ return [
1331
+ buildStep("r0", {
1332
+ url: CATALOG_SEARCH_URL,
1333
+ requestPostData: '{"page":1}',
1334
+ responseBody: {
1335
+ results: [{ orderId: "order-a" }, { orderId: "order-b" }],
1336
+ },
1337
+ timestamp: "2024-10-01T00:00:00Z",
1338
+ }),
1339
+ buildStep("r1", {
1340
+ url: CATALOG_ORDER_STATUS_URL,
1341
+ requestPostData: '{"orderId":"order-a"}',
1342
+ responseBody: ["status-token-order-a"],
1343
+ timestamp: "2024-10-01T00:00:01Z",
1344
+ }),
1345
+ buildStep("r2", {
1346
+ url: ORDER_HISTORY_URL,
1347
+ requestPostData: '{"statusToken":"status-token-order-a"}',
1348
+ responseBody: {
1349
+ entries: [
1350
+ { statusToken: "status-token-order-a", ts: "2024-10-01T00:00:02Z", event: "shipped" },
1351
+ ],
1352
+ },
1353
+ timestamp: "2024-10-01T00:00:02Z",
1354
+ }),
1355
+ ];
1356
+ }
1357
+ /**
1358
+ * A 4-step sibling of
1359
+ * {@link buildMulticallSingleShotSearchDrillDownOpaqueIntermediateChainedDependentActionSteps}
1360
+ * that inserts one more chain hop between the opaque token step and the real
1361
+ * per-item terminal: a flat confirmation object (`r2`) that echoes back the
1362
+ * token it was called with (excluded from its richness by
1363
+ * `directPrimitiveChildCountExcludingEchoed`) alongside a boolean status flag
1364
+ * AND a second, non-echoed token that threads onward into the real terminal
1365
+ * step (`r3`). Both `r2` and `r3` land on the SAME richness — two non-echoed
1366
+ * primitive fields apiece (`held`/`receiptToken` on `r2`, `event`/`ts` on
1367
+ * `r3`) — so `computeFoldChain`'s strict `candidateRichness > chainTerminalRichness`
1368
+ * comparison never lets `r3` displace `r2` as the chain terminal: a tie is
1369
+ * exactly as "not richer" as a loss. This isolates the case where a
1370
+ * side-effect confirmation hop that merely happens to match the real
1371
+ * terminal's field count silently masks it, as opposed to
1372
+ * {@link buildMulticallSingleShotSearchDrillDownOpaqueIntermediateChainedDependentActionSteps}'s
1373
+ * opaque hop, which offers no candidate at all.
1374
+ */
1375
+ function buildMulticallSingleShotSearchDrillDownRichnessTiedConfirmationHopChainedDependentActionSteps() {
1376
+ return [
1377
+ buildStep("r0", {
1378
+ url: CATALOG_SEARCH_URL,
1379
+ requestPostData: '{"page":1}',
1380
+ responseBody: {
1381
+ results: [{ orderId: "order-a" }, { orderId: "order-b" }],
1382
+ },
1383
+ timestamp: "2024-10-02T00:00:00Z",
1384
+ }),
1385
+ buildStep("r1", {
1386
+ url: CATALOG_ORDER_STATUS_URL,
1387
+ requestPostData: '{"orderId":"order-a"}',
1388
+ responseBody: ["status-token-order-a"],
1389
+ timestamp: "2024-10-02T00:00:01Z",
1390
+ }),
1391
+ buildStep("r2", {
1392
+ url: ORDER_HISTORY_URL,
1393
+ requestPostData: '{"statusToken":"status-token-order-a"}',
1394
+ responseBody: {
1395
+ statusToken: "status-token-order-a",
1396
+ held: true,
1397
+ receiptToken: "receipt-token-order-a",
1398
+ },
1399
+ timestamp: "2024-10-02T00:00:02Z",
1400
+ }),
1401
+ buildStep("r3", {
1402
+ url: `${ORDER_HISTORY_URL}receipt/`,
1403
+ requestPostData: '{"receiptToken":"receipt-token-order-a"}',
1404
+ responseBody: {
1405
+ entries: [
1406
+ {
1407
+ receiptToken: "receipt-token-order-a",
1408
+ event: "shipped",
1409
+ ts: "2024-10-02T00:00:03Z",
1410
+ },
1411
+ ],
1412
+ },
1413
+ timestamp: "2024-10-02T00:00:03Z",
1414
+ }),
1415
+ ];
1416
+ }
1417
+ const ORDER_HISTORY_BULK_URL = "https://api.example.com/orders/history/bulk";
1418
+ /**
1419
+ * An array-wrapped sibling of
1420
+ * {@link buildMulticallSingleShotSearchDrillDownOpaqueIntermediateChainedDependentActionSteps}:
1421
+ * the opaque intermediate hop (`r1`) is identical, but the chain's TERMINAL
1422
+ * hop (`r2`) threads `r1`'s produced `statusToken` wrapped inside a
1423
+ * single-element request-body ARRAY (`{"tokens":["status-token-order-a"]}`,
1424
+ * a bulk/batch-lookup request shape) instead of as a flat top-level scalar
1425
+ * field. `computeFoldChain`'s structural `dependsOnChain` detection (which
1426
+ * walks every request-body leaf regardless of nesting) resolves the chain
1427
+ * correctly either way, so this isolates a DIFFERENT failure: the
1428
+ * request-body render must still be able to thread `statusToken` into that
1429
+ * array slot instead of freezing the whole array as an opaque caller-payload
1430
+ * blob (`applyStructuredValuePayloadSubstitutions`'s "swallow whole
1431
+ * caller-supplied array/object" mechanism, which runs BEFORE state
1432
+ * threading and has no concept of "this array actually wraps a threaded
1433
+ * dependent-drill-down join value").
1434
+ */
1435
+ function buildMulticallSingleShotSearchDrillDownArrayWrappedChainedDependentActionSteps() {
1436
+ return [
1437
+ buildStep("r0", {
1438
+ url: CATALOG_SEARCH_URL,
1439
+ requestPostData: '{"page":1}',
1440
+ responseBody: {
1441
+ results: [{ orderId: "order-a" }, { orderId: "order-b" }],
1442
+ },
1443
+ timestamp: "2024-10-03T00:00:00Z",
1444
+ }),
1445
+ buildStep("r1", {
1446
+ url: CATALOG_ORDER_STATUS_URL,
1447
+ requestPostData: '{"orderId":"order-a"}',
1448
+ responseBody: ["status-token-order-a"],
1449
+ timestamp: "2024-10-03T00:00:01Z",
1450
+ }),
1451
+ buildStep("r2", {
1452
+ url: ORDER_HISTORY_BULK_URL,
1453
+ requestPostData: '{"tokens":["status-token-order-a"]}',
1454
+ responseBody: {
1455
+ entries: [
1456
+ { statusToken: "status-token-order-a", ts: "2024-10-03T00:00:02Z", event: "shipped" },
1457
+ ],
1458
+ },
1459
+ timestamp: "2024-10-03T00:00:02Z",
1460
+ }),
1461
+ ];
1462
+ }
1463
+ /**
1464
+ * Same array-wrapped-chained shape as
1465
+ * {@link buildMulticallSingleShotSearchDrillDownArrayWrappedChainedDependentActionSteps},
1466
+ * but the chain-produced value threaded from `r1` into `r2` is a bare JSON
1467
+ * NUMBER (`12345678`) rather than a string. Unlike the primary-item join
1468
+ * field covered by
1469
+ * {@link buildMulticallSingleShotSearchDrillDownArrayWrappedNumericImmediateJoinFieldActionSteps},
1470
+ * a chain-produced value must first be indexed as a threadable STATE value by
1471
+ * `indexStateValues` before any array-wrap rendering question can even arise
1472
+ * — and `indexStateValues` walks response bodies via `walkStringLeaves`,
1473
+ * which yields string leaves only. A bare-number response leaf is therefore
1474
+ * never indexed and never appears in `compileActionSteps`' `produces[]` at
1475
+ * all, so `r2`'s templated body has no accessor to substitute and renders
1476
+ * `{"tokens":[undefined]}` — invalid JSON, thrown as a fetch failure. This is
1477
+ * a distinct, more fundamental gap than the array-wrap fix
1478
+ * (`applyStructuredValuePayloadSubstitutions`, bugfix-001) addresses: it sits
1479
+ * upstream, in state INDEXING, not in payload-substitution SPARING, and
1480
+ * reproduces identically whether or not the terminal body is array-wrapped.
1481
+ */
1482
+ function buildMulticallSingleShotSearchDrillDownArrayWrappedNumericChainedJoinFieldActionSteps() {
1483
+ return [
1484
+ buildStep("r0", {
1485
+ url: CATALOG_SEARCH_URL,
1486
+ requestPostData: '{"page":1}',
1487
+ responseBody: {
1488
+ results: [{ orderId: "order-a" }, { orderId: "order-b" }],
1489
+ },
1490
+ timestamp: "2024-10-03T00:00:00Z",
1491
+ }),
1492
+ buildStep("r1", {
1493
+ url: CATALOG_ORDER_STATUS_URL,
1494
+ requestPostData: '{"orderId":"order-a"}',
1495
+ responseBody: [12345678],
1496
+ timestamp: "2024-10-03T00:00:01Z",
1497
+ }),
1498
+ buildStep("r2", {
1499
+ url: ORDER_HISTORY_BULK_URL,
1500
+ requestPostData: '{"tokens":[12345678]}',
1501
+ responseBody: {
1502
+ entries: [{ statusToken: 12345678, ts: "2024-10-03T00:00:02Z", event: "shipped" }],
1503
+ },
1504
+ timestamp: "2024-10-03T00:00:02Z",
1505
+ }),
1506
+ ];
1507
+ }
1508
+ /**
1509
+ * A different sibling of the same array-wrapped-join-field failure family:
1510
+ * here it's the IMMEDIATE drill step (`r1`), not a later chain hop, whose
1511
+ * request body wraps the value it threads — but that value is the PRIMARY
1512
+ * ITEM's own join field (`orderId`), not a prior step's produced response
1513
+ * value. `applyStructuredValuePayloadSubstitutions`'s "spare a candidate
1514
+ * already threaded" exception only recognized prior-step state values, so it
1515
+ * still froze `r1`'s `{"orderIds":["order-a"]}` into an opaque
1516
+ * `${JSON.stringify(payload.orderIds)}` blob, destroying the literal
1517
+ * `"order-a"` text the fold-loop's own per-item `parameterize` pass (which
1518
+ * runs even later, once rendering enters the loop) needs to find and swap
1519
+ * for `${item.orderId}` — every iteration replayed the SAME captured order
1520
+ * instead of each primary item's own.
1521
+ */
1522
+ function buildMulticallSingleShotSearchDrillDownArrayWrappedImmediateJoinFieldActionSteps() {
1523
+ return [
1524
+ buildStep("r0", {
1525
+ url: CATALOG_SEARCH_URL,
1526
+ requestPostData: '{"page":1}',
1527
+ responseBody: {
1528
+ results: [{ orderId: "order-a" }, { orderId: "order-b" }],
1529
+ },
1530
+ timestamp: "2024-10-03T00:00:00Z",
1531
+ }),
1532
+ buildStep("r1", {
1533
+ url: CATALOG_ORDER_STATUS_URL,
1534
+ requestPostData: '{"orderIds":["order-a"]}',
1535
+ responseBody: ["status-token-order-a"],
1536
+ timestamp: "2024-10-03T00:00:01Z",
1537
+ }),
1538
+ buildStep("r2", {
1539
+ url: ORDER_HISTORY_BULK_URL,
1540
+ requestPostData: '{"tokens":["status-token-order-a"]}',
1541
+ responseBody: {
1542
+ entries: [
1543
+ { statusToken: "status-token-order-a", ts: "2024-10-03T00:00:02Z", event: "shipped" },
1544
+ ],
1545
+ },
1546
+ timestamp: "2024-10-03T00:00:02Z",
1547
+ }),
1548
+ ];
1549
+ }
1550
+ /**
1551
+ * Same array-wrapped-immediate-join-field shape as
1552
+ * {@link buildMulticallSingleShotSearchDrillDownArrayWrappedImmediateJoinFieldActionSteps},
1553
+ * but the primary item's join field is a bare JSON NUMBER (`orderId: 101`)
1554
+ * rather than a string. `walkStringLeaves` (used by the spare check in
1555
+ * `applyStructuredValuePayloadSubstitutions`) silently skips number leaves,
1556
+ * so a numeric join value wrapped in `{"orderIds":[101]}` was frozen into an
1557
+ * opaque `${JSON.stringify(payload.orderIds)}` blob instead of being spared
1558
+ * for the fold-loop's later per-item `parameterize` pass.
1559
+ */
1560
+ function buildMulticallSingleShotSearchDrillDownArrayWrappedNumericImmediateJoinFieldActionSteps() {
1561
+ return [
1562
+ buildStep("r0", {
1563
+ url: CATALOG_SEARCH_URL,
1564
+ requestPostData: '{"page":1}',
1565
+ responseBody: {
1566
+ results: [{ orderId: 101 }, { orderId: 102 }],
1567
+ },
1568
+ timestamp: "2024-10-03T00:00:00Z",
1569
+ }),
1570
+ buildStep("r1", {
1571
+ url: CATALOG_ORDER_STATUS_URL,
1572
+ requestPostData: '{"orderIds":[101]}',
1573
+ responseBody: ["status-token-101"],
1574
+ timestamp: "2024-10-03T00:00:01Z",
1575
+ }),
1576
+ buildStep("r2", {
1577
+ url: ORDER_HISTORY_BULK_URL,
1578
+ requestPostData: '{"tokens":["status-token-101"]}',
1579
+ responseBody: {
1580
+ entries: [
1581
+ { statusToken: "status-token-101", ts: "2024-10-03T00:00:02Z", event: "shipped" },
1582
+ ],
1583
+ },
1584
+ timestamp: "2024-10-03T00:00:02Z",
1585
+ }),
1586
+ ];
1587
+ }
149
1588
  //# sourceMappingURL=recon-generate-multicall-fixture.js.map