@enricai/barnacle 1.12.64 → 1.12.65

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.
@@ -4493,7 +4493,15 @@ function replaceGuardedAgainstExistingPlaceholders(text, pattern, bindingByValue
4493
4493
  * first, and the literal is gone by the time the KV pass runs — masking
4494
4494
  * payload precedence on every call after the one that scraped it.
4495
4495
  */
4496
- function interpolateStateValues(template, priorSteps, targetCapture, payloadAccessorByValue = new Map(), isJsonBody = false, producerBoundaryBindings = new Map(), stepIndex = -1, topLevelPayloadKvValues = new Set()) {
4496
+ function interpolateStateValues(template, priorSteps, targetCapture, payloadAccessorByValue = new Map(), isJsonBody = false, producerBoundaryBindings = new Map(), stepIndex = -1, topLevelPayloadKvValues = new Set(),
4497
+ // A fold target's own join-field value (see `joinFieldValuesByStep` in
4498
+ // `emitMultiStepExecuteHttp`) must stay a literal here — binding it to a
4499
+ // prior step's produced state var (e.g. `${productId}`, always the FIRST
4500
+ // primary item's value) freezes every per-item drill call onto that one
4501
+ // item; the fold loop's own later `parameterize` pass is what rebinds the
4502
+ // literal to `${item.<field>}` per iteration, but only if the literal
4503
+ // text survives this pass untouched.
4504
+ excludedJoinFieldValues = new Set()) {
4497
4505
  const stateBindings = deriveStateVarByValue(priorSteps, targetCapture);
4498
4506
  const bindingByValue = new Map();
4499
4507
  for (const [value, accessor] of payloadAccessorByValue) {
@@ -4508,6 +4516,8 @@ function interpolateStateValues(template, priorSteps, targetCapture, payloadAcce
4508
4516
  continue;
4509
4517
  if (topLevelPayloadKvValues.has(value) && !isProducerStep)
4510
4518
  continue;
4519
+ if (excludedJoinFieldValues.has(value) && !isProducerStep)
4520
+ continue;
4511
4521
  bindingByValue.set(value, `\${${binding.varName}}`);
4512
4522
  sourceNameByValue.set(value, binding.sourceName);
4513
4523
  if (binding.restricted)
@@ -5525,7 +5535,7 @@ pascalName = null) {
5525
5535
  // length/chain-eligibility scoping doesn't happen to catch the coincidence.
5526
5536
  const url = (0, capture_filters_1.isZeroVarianceRepeatCapture)(cap, actions.map((a) => a.capture))
5527
5537
  ? cap.url
5528
- : interpolateStateValues(cap.url, prior, cap, payloadAccessorByValue, false, producerBoundaryBindings, i);
5538
+ : interpolateStateValues(cap.url, prior, cap, payloadAccessorByValue, false, producerBoundaryBindings, i, new Set(), joinFieldValuesByStep.get(i) ?? new Set());
5529
5539
  // Form-schema substitution runs first on the raw recon body so its
5530
5540
  // field-id-anchored matches see the original JSON. State-threading and
5531
5541
  // payload key-value passes then run on top. Option-id substitution runs
@@ -5595,7 +5605,7 @@ pascalName = null) {
5595
5605
  ? applyUrlParamPayloadSubstitutions(rawBodyWithProducerBoundary, parsedBody, urlParamBindings)
5596
5606
  : rawBodyWithProducerBoundary;
5597
5607
  const bodyAfterStateAndKv = rawBodyWithUrlParams
5598
- ? applyPayloadKeyValueSubstitutions(interpolateStateValues(rawBodyWithUrlParams, prior, cap, payloadAccessorByValue, true, producerBoundaryBindings, i, topLevelPayloadKvValues), inputBody, additionalBodies, outDiscoveredAdditionalBodyKeys, allResponseBodies)
5608
+ ? applyPayloadKeyValueSubstitutions(interpolateStateValues(rawBodyWithUrlParams, prior, cap, payloadAccessorByValue, true, producerBoundaryBindings, i, topLevelPayloadKvValues, joinFieldValuesByStep.get(i) ?? new Set()), inputBody, additionalBodies, outDiscoveredAdditionalBodyKeys, allResponseBodies)
5599
5609
  : "";
5600
5610
  // Mechanism A — generic (plain-JSON, wire-key-anchored) dropdown label→code
5601
5611
  // rewrite. Runs AFTER interpolateStateValues + the payload-KV pass, not
@@ -8190,7 +8200,26 @@ function compileFoldReturnResultsMatcher(spec) {
8190
8200
  function buildFoldPlanFromSpec(actions, spec,
8191
8201
  // See detectDrillDownFoldPlan's identical parameter — constrains this
8192
8202
  // function's own primaryStepIndex loop to the same anchor identity.
8193
- primaryIdentityAnchor = null) {
8203
+ primaryIdentityAnchor = null,
8204
+ // When given, restricts the loop to this ONE primaryStepIndex instead of
8205
+ // scanning every occurrence and keeping only the freshest. Used by
8206
+ // mergeSpecPlanOntoSamePrimary to resolve a declared joinFields override
8207
+ // independently for EACH re-issued/paginated primary occurrence, since the
8208
+ // freshest-wins scan below only ever resolves the LAST occurrence.
8209
+ exactPrimaryStepIndex = null,
8210
+ // When given, restricts the matching-drill scan below to candidates whose
8211
+ // OWN endpoint (not a later chain hop) matches this key. A single primary
8212
+ // occurrence can structurally resolve MULTIPLE independent per-item
8213
+ // targets — one per distinct drill endpoint (e.g. a genuinely per-item
8214
+ // drill loop where each item's own request lands on the SAME endpoint
8215
+ // pattern but a distinct path/endpoint identity) — and this function's own
8216
+ // freshest-first scan below `break`s after the FIRST endpoint it resolves,
8217
+ // so an unrestricted call can only ever return an override for ONE of
8218
+ // them. Used by mergeSpecPlanOntoSamePrimary to resolve the declared
8219
+ // joinFields override independently for EACH structural target, keyed by
8220
+ // its own drill endpoint, instead of only the one this function's
8221
+ // freshest-first scan happens to land on first.
8222
+ restrictToDrillEndpointKey = null) {
8194
8223
  const primaryArrayPath = spec.resultsPath.split(".");
8195
8224
  const matchesFoldReturnEndpoint = compileFoldReturnEndpointMatcher(spec);
8196
8225
  // Built once and shared across every primaryStepIndex — see
@@ -8201,6 +8230,9 @@ primaryIdentityAnchor = null) {
8201
8230
  const requestValueIndex = buildRequestValueIndex(actions);
8202
8231
  let freshestPlan = null;
8203
8232
  for (let primaryStepIndex = 0; primaryStepIndex < actions.length; primaryStepIndex++) {
8233
+ if (exactPrimaryStepIndex !== null && primaryStepIndex !== exactPrimaryStepIndex) {
8234
+ continue;
8235
+ }
8204
8236
  if (primaryIdentityAnchor !== null &&
8205
8237
  operationGroupKey(actions[primaryStepIndex].capture) !== primaryIdentityAnchor) {
8206
8238
  continue;
@@ -8229,7 +8261,9 @@ primaryIdentityAnchor = null) {
8229
8261
  // response-only fallback below exists to resolve.
8230
8262
  const matchingDrillStepIndices = [];
8231
8263
  for (let drillStepIndex = primaryStepIndex + 1; drillStepIndex < actions.length; drillStepIndex++) {
8232
- if (matchesFoldReturnEndpoint(actions[drillStepIndex].capture)) {
8264
+ if (matchesFoldReturnEndpoint(actions[drillStepIndex].capture) &&
8265
+ (restrictToDrillEndpointKey === null ||
8266
+ endpointKey(actions[drillStepIndex].capture.url) === restrictToDrillEndpointKey)) {
8233
8267
  matchingDrillStepIndices.push(drillStepIndex);
8234
8268
  }
8235
8269
  }
@@ -8363,6 +8397,26 @@ function isDescendantArrayPath(candidate, base) {
8363
8397
  * spec's own drill indices look independent, so the heuristic's wrong-level
8364
8398
  * guess never survives alongside the corrected one.
8365
8399
  */
8400
+ /**
8401
+ * The stable identity of the logical drill-down a {@link FoldTarget}
8402
+ * resolves onto: the endpoint of its chain's TERMINAL call (the response
8403
+ * actually folded — see {@link FoldTarget.chainTerminalIndex}) paired with
8404
+ * the specific array read off that response (see
8405
+ * {@link FoldTarget.chainArrayPath}). Neither piece alone is stable enough to
8406
+ * compare two independently-resolved targets by: `drillStepIndex` (the
8407
+ * chain's ENTRY point) can legitimately differ between the structural
8408
+ * heuristic's own request-threading resolution and a spec's
8409
+ * response-only/upstream-hop resolution even when both terminate at the same
8410
+ * call (see {@link buildFoldPlanFromSpec}'s `entryIndex` docstring); and the
8411
+ * terminal endpoint alone can collide when one response happens to carry TWO
8412
+ * unrelated per-item arrays for two entirely different primaries (e.g. a
8413
+ * combined status response naming both `entries` and `contracts`). Pairing
8414
+ * the terminal's endpoint with the specific array it reads disambiguates
8415
+ * both cases at once.
8416
+ */
8417
+ function foldTargetDrillIdentity(actions, target) {
8418
+ return `${endpointKey(actions[target.chainTerminalIndex].capture.url)}\u0000${target.chainArrayPath.join(".")}`;
8419
+ }
8366
8420
  function mergeSpecPlanOntoSamePrimary(structuralPlans, actions, foldReturnSpec,
8367
8421
  // See detectDrillDownFoldPlan's identical parameter. Threaded into this
8368
8422
  // function's own buildFoldPlanFromSpec call so a spec plan resolved here
@@ -8371,9 +8425,55 @@ function mergeSpecPlanOntoSamePrimary(structuralPlans, actions, foldReturnSpec,
8371
8425
  primaryIdentityAnchor = null) {
8372
8426
  if (foldReturnSpec === null)
8373
8427
  return [...structuralPlans];
8428
+ // Override joinFields on EVERY structural primary occurrence the spec can
8429
+ // independently resolve at its OWN primaryStepIndex — not just whichever
8430
+ // single occurrence the freshest-wins `specPlan` resolution below happens
8431
+ // to land on. A re-issued/paginated primary (same endpoint, multiple
8432
+ // captures) produces one independent structural plan per occurrence; each
8433
+ // must get the declared override, not just the freshest one.
8434
+ const structuralPlansWithPerOccurrenceOverrides = structuralPlans.map((plan) => {
8435
+ // Resolved once per structural TARGET (keyed on that target's own drill
8436
+ // endpoint), not once for the whole primary occurrence: buildFoldPlanFromSpec's
8437
+ // freshest-first scan `break`s after the first drill endpoint it resolves, so a
8438
+ // single unrestricted call can only ever produce an override for ONE of a
8439
+ // primary's structural targets even when it has several (e.g. a genuinely
8440
+ // per-item drill loop resolving one independent target per item, each at its
8441
+ // own endpoint). See buildFoldPlanFromSpec's restrictToDrillEndpointKey docstring.
8442
+ const mergedTargets = plan.targets.map((target) => {
8443
+ const ownSpecPlan = buildFoldPlanFromSpec(actions, foldReturnSpec, primaryIdentityAnchor, plan.primaryStepIndex, endpointKey(actions[target.drillStepIndex].capture.url));
8444
+ if (ownSpecPlan === null ||
8445
+ JSON.stringify(ownSpecPlan.primaryArrayPath) !== JSON.stringify(plan.primaryArrayPath)) {
8446
+ return target;
8447
+ }
8448
+ // ownSpecPlan was resolved with restrictToDrillEndpointKey pinned to
8449
+ // this target's OWN drill endpoint, so its single target (see
8450
+ // buildFoldPlanFromSpec's `targets: [{...}]` literal — it never
8451
+ // returns more than one) IS the spec's resolution of this restricted
8452
+ // endpoint by construction. Matching it back to `target` via
8453
+ // foldTargetDrillIdentity is not just redundant but actively wrong: a
8454
+ // multi-hop drill chain can make the spec's forward walk
8455
+ // (buildFoldPlanFromSpec's computeFoldChain resolution) land on a
8456
+ // different chainTerminalIndex/chainArrayPath than the structural
8457
+ // heuristic did for the exact same restricted endpoint, so the
8458
+ // identity lookup misses and the declared joinFields override is
8459
+ // silently dropped in favor of the heuristic's structural guess.
8460
+ const specTarget = ownSpecPlan.targets[0];
8461
+ return specTarget === undefined ? target : { ...target, joinFields: specTarget.joinFields };
8462
+ });
8463
+ // A spec-declared target the heuristic missed entirely (an independent
8464
+ // drill-down at a NEW endpoint) is still found via the original
8465
+ // unrestricted resolution, which is free to land on any endpoint.
8466
+ const ownSpecPlan = buildFoldPlanFromSpec(actions, foldReturnSpec, primaryIdentityAnchor, plan.primaryStepIndex);
8467
+ const existingDrillIdentities = new Set(plan.targets.map((target) => foldTargetDrillIdentity(actions, target)));
8468
+ const newTargets = ownSpecPlan !== null &&
8469
+ JSON.stringify(ownSpecPlan.primaryArrayPath) === JSON.stringify(plan.primaryArrayPath)
8470
+ ? ownSpecPlan.targets.filter((target) => !existingDrillIdentities.has(foldTargetDrillIdentity(actions, target)))
8471
+ : [];
8472
+ return { ...plan, targets: [...mergedTargets, ...newTargets] };
8473
+ });
8374
8474
  const specPlan = buildFoldPlanFromSpec(actions, foldReturnSpec, primaryIdentityAnchor);
8375
8475
  if (specPlan === null)
8376
- return [...structuralPlans];
8476
+ return structuralPlansWithPerOccurrenceOverrides;
8377
8477
  const specPrimaryEndpointKey = endpointKey(actions[specPlan.primaryStepIndex].capture.url);
8378
8478
  // Deliberately keyed on the RAW primaryStepIndex (not endpoint identity):
8379
8479
  // this branch trusts the structural target's own chain/drillArrayPath and
@@ -8383,7 +8483,7 @@ primaryIdentityAnchor = null) {
8383
8483
  // different index) may have matched a different item entirely, so it must
8384
8484
  // fall through to the wholesale-replace (sharedDrillPlan) or union
8385
8485
  // (consumedPrimarySteps) handling below instead of merging by identity.
8386
- const samePrimaryPlan = structuralPlans.find((plan) => plan.primaryStepIndex === specPlan.primaryStepIndex &&
8486
+ const samePrimaryPlan = structuralPlansWithPerOccurrenceOverrides.find((plan) => plan.primaryStepIndex === specPlan.primaryStepIndex &&
8387
8487
  JSON.stringify(plan.primaryArrayPath) === JSON.stringify(specPlan.primaryArrayPath));
8388
8488
  if (samePrimaryPlan !== undefined) {
8389
8489
  // A structural target serving the SAME drill-down the spec declares
@@ -8403,38 +8503,53 @@ primaryIdentityAnchor = null) {
8403
8503
  // but equally valid, occurrence of the SAME re-issued/paginated drill
8404
8504
  // endpoint — raw-index equality only happened to override when both
8405
8505
  // resolutions picked the identical occurrence, silently leaving every
8406
- // other occurrence's joinFields un-overridden.
8407
- const specTargetsByDrillEndpointKey = new Map(specPlan.targets.map((target) => [
8408
- endpointKey(actions[target.drillStepIndex].capture.url),
8409
- target,
8410
- ]));
8411
- return structuralPlans.map((plan) => {
8506
+ // other occurrence's joinFields un-overridden. Keyed on each target's
8507
+ // {@link foldTargetDrillIdentity} — the terminal endpoint paired with the
8508
+ // specific array it reads — rather than `drillStepIndex`: a spec's
8509
+ // declared join value often resolves only through an upstream entry hop
8510
+ // (e.g. a header-threaded token) that isn't the endpointPattern-matched
8511
+ // call itself, so its `drillStepIndex` (== that entry hop) can
8512
+ // legitimately differ from the structural heuristic's own
8513
+ // `drillStepIndex` (its own, possibly different, threading entry point)
8514
+ // even when both chains terminate at the exact same logical drill-down
8515
+ // call and array.
8516
+ const specTargetsByDrillIdentity = new Map(specPlan.targets.map((target) => [foldTargetDrillIdentity(actions, target), target]));
8517
+ return structuralPlansWithPerOccurrenceOverrides.map((plan) => {
8412
8518
  if (plan !== samePrimaryPlan)
8413
8519
  return plan;
8414
8520
  const mergedTargets = plan.targets.map((target) => {
8415
- const specTarget = specTargetsByDrillEndpointKey.get(endpointKey(actions[target.drillStepIndex].capture.url));
8521
+ const specTarget = specTargetsByDrillIdentity.get(foldTargetDrillIdentity(actions, target));
8416
8522
  return specTarget === undefined ? target : { ...target, joinFields: specTarget.joinFields };
8417
8523
  });
8418
- const existingDrillEndpointKeys = new Set(plan.targets.map((target) => endpointKey(actions[target.drillStepIndex].capture.url)));
8419
- const newTargets = specPlan.targets.filter((target) => !existingDrillEndpointKeys.has(endpointKey(actions[target.drillStepIndex].capture.url)));
8524
+ const existingDrillIdentities = new Set(plan.targets.map((target) => foldTargetDrillIdentity(actions, target)));
8525
+ const newTargets = specPlan.targets.filter((target) => !existingDrillIdentities.has(foldTargetDrillIdentity(actions, target)));
8420
8526
  return { ...plan, targets: [...mergedTargets, ...newTargets] };
8421
8527
  });
8422
8528
  }
8423
8529
  // No exact (primaryStepIndex, primaryArrayPath) match, but a structural
8424
8530
  // plan on the SAME primaryStepIndex may still have independently detected
8425
- // a target at the SAME drillStepIndex a spec target names — just at the
8426
- // wrong array level (e.g. a shallower structural guess that happens to
8427
- // share the spec's drill-down call). That structural target's own
8428
- // primaryArrayPath/drillArrayPath/joinFields describe the heuristic's
8429
- // guess, not the flow author's declaration, so the spec target replaces
8430
- // it wholesale rather than being discarded by the mismatched-array-path
8431
- // branch below or partially merged as if the array level matched.
8432
- const specDrillStepIndices = new Set(specPlan.targets.map((target) => target.drillStepIndex));
8433
- const sharedDrillPlan = structuralPlans.find((plan) => endpointKey(actions[plan.primaryStepIndex].capture.url) === specPrimaryEndpointKey &&
8434
- plan.targets.some((target) => specDrillStepIndices.has(target.drillStepIndex)));
8531
+ // a target at the SAME drill ENDPOINT+array a spec target names — just at
8532
+ // the wrong array level (e.g. a shallower structural guess that happens to
8533
+ // share the spec's drill-down call). Matched by {@link
8534
+ // foldTargetDrillIdentity} (see the samePrimaryPlan branch above for why
8535
+ // the terminal+array, not the entry `drillStepIndex` alone, is the stable
8536
+ // identity to compare on) — not raw drillStepIndex equality:
8537
+ // buildFoldPlanFromSpec's own entryIndex resolution and the structural
8538
+ // heuristic's request-threading scan can each land on a different, but
8539
+ // equally valid, occurrence/hop of the SAME endpoint (or the same single
8540
+ // occurrence reached via a different chain entry point), and raw-index
8541
+ // equality only happens to hold when both resolutions pick the identical
8542
+ // index. That structural target's own primaryArrayPath/drillArrayPath/
8543
+ // joinFields describe the heuristic's guess, not the flow author's
8544
+ // declaration, so the spec target replaces it wholesale rather than being
8545
+ // discarded by the mismatched-array-path branch below or partially merged
8546
+ // as if the array level matched.
8547
+ const specDrillIdentities = new Set(specPlan.targets.map((target) => foldTargetDrillIdentity(actions, target)));
8548
+ const sharedDrillPlan = structuralPlansWithPerOccurrenceOverrides.find((plan) => endpointKey(actions[plan.primaryStepIndex].capture.url) === specPrimaryEndpointKey &&
8549
+ plan.targets.some((target) => specDrillIdentities.has(foldTargetDrillIdentity(actions, target))));
8435
8550
  if (sharedDrillPlan !== undefined) {
8436
- const remainingTargets = sharedDrillPlan.targets.filter((target) => !specDrillStepIndices.has(target.drillStepIndex));
8437
- const otherPlans = structuralPlans.filter((plan) => plan !== sharedDrillPlan);
8551
+ const remainingTargets = sharedDrillPlan.targets.filter((target) => !specDrillIdentities.has(foldTargetDrillIdentity(actions, target)));
8552
+ const otherPlans = structuralPlansWithPerOccurrenceOverrides.filter((plan) => plan !== sharedDrillPlan);
8438
8553
  const keptStructuralPlans = remainingTargets.length === 0
8439
8554
  ? otherPlans
8440
8555
  : [...otherPlans, { ...sharedDrillPlan, targets: remainingTargets }];
@@ -8449,10 +8564,13 @@ primaryIdentityAnchor = null) {
8449
8564
  // the independence guard below would otherwise do, since a
8450
8565
  // level-correcting spec's own drill indices can look entirely
8451
8566
  // independent) or silently dropped by the mismatched-array-path checks.
8452
- const supersededShallowerPlan = structuralPlans.find((plan) => endpointKey(actions[plan.primaryStepIndex].capture.url) === specPrimaryEndpointKey &&
8567
+ const supersededShallowerPlan = structuralPlansWithPerOccurrenceOverrides.find((plan) => endpointKey(actions[plan.primaryStepIndex].capture.url) === specPrimaryEndpointKey &&
8453
8568
  isDescendantArrayPath(specPlan.primaryArrayPath, plan.primaryArrayPath));
8454
8569
  if (supersededShallowerPlan !== undefined) {
8455
- return [...structuralPlans.filter((plan) => plan !== supersededShallowerPlan), specPlan];
8570
+ return [
8571
+ ...structuralPlansWithPerOccurrenceOverrides.filter((plan) => plan !== supersededShallowerPlan),
8572
+ specPlan,
8573
+ ];
8456
8574
  }
8457
8575
  // Keyed by the (primaryStepIndex, primaryArrayPath) pair, not
8458
8576
  // primaryStepIndex alone — a structural plan only ever consumed ITS OWN
@@ -8469,7 +8587,7 @@ primaryIdentityAnchor = null) {
8469
8587
  // contends for.
8470
8588
  const consumedIndices = new Set();
8471
8589
  const consumedPrimarySteps = new Set();
8472
- for (const plan of structuralPlans) {
8590
+ for (const plan of structuralPlansWithPerOccurrenceOverrides) {
8473
8591
  const planPrimaryEndpointKey = endpointKey(actions[plan.primaryStepIndex].capture.url);
8474
8592
  consumedPrimarySteps.add(`${planPrimaryEndpointKey}:${JSON.stringify(plan.primaryArrayPath)}`);
8475
8593
  for (const target of plan.targets) {
@@ -8480,19 +8598,27 @@ primaryIdentityAnchor = null) {
8480
8598
  !consumedPrimarySteps.has(`${specPrimaryEndpointKey}:${JSON.stringify(specPlan.primaryArrayPath)}`) &&
8481
8599
  specPlan.targets.every((target) => target.chain.every((chainIndex) => !consumedIndices.has(chainIndex)));
8482
8600
  if (specConsumesOnlyItsOwnIndices)
8483
- return [...structuralPlans, specPlan];
8601
+ return [...structuralPlansWithPerOccurrenceOverrides, specPlan];
8484
8602
  // The spec's primary endpoint/array is already consumed by a structural
8485
8603
  // plan on a DIFFERENT capture of the same re-issued primary operation
8486
8604
  // (paginated primary), but its own target chains touch no index that plan
8487
8605
  // already depends on — this is the same identity, not a raw-index
8488
8606
  // coincidence, so union the spec's targets onto it rather than silently
8489
- // dropping a declaration the flow author explicitly wrote.
8490
- const sameIdentityPlan = structuralPlans.find((plan) => endpointKey(actions[plan.primaryStepIndex].capture.url) === specPrimaryEndpointKey &&
8607
+ // dropping a declaration the flow author explicitly wrote. Membership in
8608
+ // "already covered" is decided by {@link foldTargetDrillIdentity} (see the
8609
+ // samePrimaryPlan branch above for why the terminal+array, not the entry
8610
+ // `drillStepIndex` alone, is the stable identity) — not raw drillStepIndex
8611
+ // equality. A spec target sharing a structural target's drill identity is
8612
+ // a re-declaration of that SAME drill-down (handled as a wholesale replace
8613
+ // by sharedDrillPlan above), not an independent one to union — so this
8614
+ // only ever unions genuinely new endpoints onto an already-identified
8615
+ // primary.
8616
+ const sameIdentityPlan = structuralPlansWithPerOccurrenceOverrides.find((plan) => endpointKey(actions[plan.primaryStepIndex].capture.url) === specPrimaryEndpointKey &&
8491
8617
  JSON.stringify(plan.primaryArrayPath) === JSON.stringify(specPlan.primaryArrayPath) &&
8492
- specPlan.targets.every((target) => !plan.targets.some((t) => t.drillStepIndex === target.drillStepIndex)));
8618
+ specPlan.targets.every((target) => !plan.targets.some((t) => foldTargetDrillIdentity(actions, t) === foldTargetDrillIdentity(actions, target))));
8493
8619
  if (sameIdentityPlan === undefined)
8494
- return [...structuralPlans];
8495
- return structuralPlans.map((plan) => plan === sameIdentityPlan ? { ...plan, targets: [...plan.targets, ...specPlan.targets] } : plan);
8620
+ return [...structuralPlansWithPerOccurrenceOverrides];
8621
+ return structuralPlansWithPerOccurrenceOverrides.map((plan) => plan === sameIdentityPlan ? { ...plan, targets: [...plan.targets, ...specPlan.targets] } : plan);
8496
8622
  }
8497
8623
  /**
8498
8624
  * The single fold-plan entry point: {@link detectDrillDownFoldPlan}'s