@enricai/barnacle 1.12.73 → 1.12.75

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.
@@ -24,6 +24,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
24
24
  exports.resolveStepPayloadField = resolveStepPayloadField;
25
25
  exports.buildKnownFieldValues = buildKnownFieldValues;
26
26
  exports.extractStepPersonaValue = extractStepPersonaValue;
27
+ exports.extractNavigateToHashFragmentValue = extractNavigateToHashFragmentValue;
27
28
  exports.deriveFillLabelField = deriveFillLabelField;
28
29
  exports.harvestPersonaBindings = harvestPersonaBindings;
29
30
  exports.resolveCompositePersonaFields = resolveCompositePersonaFields;
@@ -60,6 +61,7 @@ exports.ancestorScopedAccessor = ancestorScopedAccessor;
60
61
  exports.compileActionSteps = compileActionSteps;
61
62
  exports.collectHeaderBindings = collectHeaderBindings;
62
63
  exports.deriveProducerBoundaryBindings = deriveProducerBoundaryBindings;
64
+ exports.collectUnboundLiterals = collectUnboundLiterals;
63
65
  exports.emitMultiStepExecuteHttp = emitMultiStepExecuteHttp;
64
66
  exports.countSpliceableFacets = countSpliceableFacets;
65
67
  exports.spliceFacetsIntoStringVariable = spliceFacetsIntoStringVariable;
@@ -359,6 +361,34 @@ function extractStepPersonaValue(instruction, env) {
359
361
  const value = pickValueSpan(instruction, spans).value;
360
362
  return value.length > 0 ? value : null;
361
363
  }
364
+ /**
365
+ * Extracts the trailing path segment of a navigateTo URL's hash fragment —
366
+ * e.g. `.../#/jobs/category-widgets` yields `widgets` — as the persona value
367
+ * an explicit `payloadField` annotation on that step names. Site-agnostic:
368
+ * reads only the URL's own hash shape, no domain knowledge.
369
+ *
370
+ * When `previousHash` is given and the current hash is a strict extension of
371
+ * it (the accumulating-hash case, e.g. `#a` -> `#a,b`), the persona value is
372
+ * only the NEW suffix the step added, not the whole cumulative string — a
373
+ * cumulative value would never recur verbatim in any single request literal.
374
+ *
375
+ * @returns the segment (or delta), or null when the URL has no non-empty
376
+ * hash segment
377
+ */
378
+ function extractNavigateToHashFragmentValue(url, previousHash) {
379
+ const hashIndex = url.indexOf("#");
380
+ if (hashIndex === -1)
381
+ return null;
382
+ const hash = url.slice(hashIndex + 1);
383
+ if (previousHash && hash.length > previousHash.length && hash.startsWith(previousHash)) {
384
+ const suffix = hash.slice(previousHash.length);
385
+ const delta = /^[,/]/.test(suffix) ? suffix.slice(1) : suffix;
386
+ if (delta.length > 0)
387
+ return delta;
388
+ }
389
+ const segments = hash.split("/").filter((s) => s.length > 0);
390
+ return segments.length > 0 ? segments[segments.length - 1] : null;
391
+ }
362
392
  /**
363
393
  * Derives a payload field name from the field LABEL in a fill/enter/type
364
394
  * instruction, for steps the consumer vocabulary does not cover.
@@ -405,9 +435,32 @@ function deriveFillLabelField(instruction) {
405
435
  function harvestPersonaBindings(flowSteps, vocabulary, env) {
406
436
  const bindings = new Map();
407
437
  const knownFieldValues = buildKnownFieldValues(flowSteps, vocabulary, env);
438
+ let previousNavigateToHash;
408
439
  for (const step of flowSteps) {
409
440
  const isObj = typeof step !== "string";
410
441
  const instruction = isObj ? step.step : step;
442
+ // A navigateTo step carries no prose instruction to extract a persona
443
+ // value FROM — its own text is a captured page.goto, not a fill/select
444
+ // label. The facet an explicit `payloadField` annotation names instead
445
+ // lives in the URL itself (recon-browser captures navigation targets like
446
+ // `.../#/jobs/category-widgets`), so the value to correlate against
447
+ // downstream header/body templates is the URL's own hash-derived
448
+ // fragment, not anything extractStepPersonaValue could find in `step.step`.
449
+ if (isObj && step.navigateTo !== undefined) {
450
+ const hashIndex = step.navigateTo.indexOf("#");
451
+ const currentHash = hashIndex === -1 ? undefined : step.navigateTo.slice(hashIndex + 1);
452
+ if (!step.payloadField) {
453
+ previousNavigateToHash = currentHash;
454
+ continue;
455
+ }
456
+ const value = extractNavigateToHashFragmentValue(step.navigateTo, previousNavigateToHash);
457
+ previousNavigateToHash = currentHash;
458
+ if (value === null)
459
+ continue;
460
+ if (!bindings.has(value))
461
+ bindings.set(value, `payload.${step.payloadField}`);
462
+ continue;
463
+ }
411
464
  const vocabField = resolveStepPayloadField(instruction, isObj ? step.payloadField : undefined, isObj ? step.payloadFieldNone : undefined, vocabulary, knownFieldValues);
412
465
  // Vocabulary wins outright. Only on a miss do we fall back to deriving the
413
466
  // field from the instruction's own label — and never when the author opted
@@ -3913,7 +3966,7 @@ function locateFormEnvelopePath(parsedBody) {
3913
3966
  * search there stops an earlier element's already-rewritten (or still-frozen)
3914
3967
  * span from swallowing a later element's identically-named key.
3915
3968
  */
3916
- function applyStructuredValuePayloadSubstitutionsForEnvelope(template, envelope, outStructuredKeys, priorStepStateValues, searchFrom, envelopePath, rootBody) {
3969
+ function applyStructuredValuePayloadSubstitutionsForEnvelope(template, envelope, outStructuredKeys, unconditionalExcludeValues, restrictedExcludeSourceByValue, searchFrom, envelopePath, rootBody) {
3917
3970
  let result = template;
3918
3971
  let cursor = searchFrom;
3919
3972
  // The body root's own entries are eligible too, whenever the located
@@ -3935,8 +3988,29 @@ function applyStructuredValuePayloadSubstitutionsForEnvelope(template, envelope,
3935
3988
  Object.keys(value).length > 0;
3936
3989
  if (!isNonEmptyArray && !isNestedObject)
3937
3990
  continue;
3938
- if (priorStepStateValues.size > 0) {
3939
- const carriesThreadedValue = [...walkAllPrimitiveLeaves(value)].some(({ value: leaf }) => leaf !== null && priorStepStateValues.has(String(leaf)));
3991
+ if (unconditionalExcludeValues.size > 0 || restrictedExcludeSourceByValue.size > 0) {
3992
+ // A name-free join/chain value (unconditionalExcludeValues) excludes the
3993
+ // whole field on ANY leaf coincidence, same as before. A RESTRICTED prior-
3994
+ // step value (one with a real source field name) additionally requires
3995
+ // keyNamesCorrelate(sourceName, key) — mirroring the discipline
3996
+ // replaceGuardedAgainstExistingPlaceholders/findThreadedJoinFields already
3997
+ // apply to the exact same class of value-coincidence false positive.
3998
+ // Without this, a broken field's own unrelated short scalar leaf (a
3999
+ // count/id) merely equalling SOME other step's produced value gets the
4000
+ // ENTIRE array/object excluded from threading, even though nothing about
4001
+ // that leaf's key names the same concept as the matched value's source.
4002
+ const carriesThreadedValue = [...walkAllPrimitiveLeaves(value)].some(({ value: leaf, path: leafPath }) => {
4003
+ if (leaf === null)
4004
+ return false;
4005
+ const leafValue = String(leaf);
4006
+ if (unconditionalExcludeValues.has(leafValue))
4007
+ return true;
4008
+ const sourceName = restrictedExcludeSourceByValue.get(leafValue);
4009
+ if (sourceName === undefined)
4010
+ return false;
4011
+ const leafKey = leafPath.at(-1);
4012
+ return leafKey !== undefined && keyNamesCorrelate(sourceName, leafKey);
4013
+ });
3940
4014
  if (carriesThreadedValue)
3941
4015
  continue;
3942
4016
  }
@@ -3993,7 +4067,7 @@ function applyStructuredValuePayloadSubstitutionsForEnvelope(template, envelope,
3993
4067
  * pass to it. Shared by the top-level-object and top-level-array branches of
3994
4068
  * {@link applyStructuredValuePayloadSubstitutions} below.
3995
4069
  */
3996
- function applyStructuredValuePayloadSubstitutionsForObjectBody(template, objectBody, outStructuredKeys, priorStepStateValues, searchFrom) {
4070
+ function applyStructuredValuePayloadSubstitutionsForObjectBody(template, objectBody, outStructuredKeys, unconditionalExcludeValues, restrictedExcludeSourceByValue, searchFrom) {
3997
4071
  const envelopePath = locateFormEnvelopePath(objectBody);
3998
4072
  let envelope = objectBody;
3999
4073
  for (const seg of envelopePath) {
@@ -4004,12 +4078,33 @@ function applyStructuredValuePayloadSubstitutionsForObjectBody(template, objectB
4004
4078
  if (envelope === null || typeof envelope !== "object" || Array.isArray(envelope)) {
4005
4079
  return { result: template, nextSearchFrom: searchFrom };
4006
4080
  }
4007
- return applyStructuredValuePayloadSubstitutionsForEnvelope(template, envelope, outStructuredKeys, priorStepStateValues, searchFrom, envelopePath, objectBody);
4081
+ return applyStructuredValuePayloadSubstitutionsForEnvelope(template, envelope, outStructuredKeys, unconditionalExcludeValues, restrictedExcludeSourceByValue, searchFrom, envelopePath, objectBody);
4008
4082
  }
4009
- function applyStructuredValuePayloadSubstitutions(template, parsedBody, outStructuredKeys, priorStepStateValues = new Set()) {
4083
+ /**
4084
+ * `priorStepStateBindings` carries the SAME value→binding map
4085
+ * {@link deriveStateVarByValue} produces. A binding with `restricted: true`
4086
+ * (a value whose source has a real field name) only excludes a candidate
4087
+ * field when its own leaf's key {@link keyNamesCorrelate}s with that source
4088
+ * name — an unrestricted binding (name-free, e.g. a bare array-index chain
4089
+ * hop) or a `joinFieldValues` entry excludes unconditionally on any leaf
4090
+ * match, same as before. Without the correlation requirement, an array/
4091
+ * object field whose own unrelated short scalar leaf (a count/id) merely
4092
+ * EQUALS some other step's produced value gets the whole field wrongly
4093
+ * excluded from threading — the same value-coincidence class of bug
4094
+ * {@link keyNamesCorrelate}'s other call sites already guard against.
4095
+ */
4096
+ function applyStructuredValuePayloadSubstitutions(template, parsedBody, outStructuredKeys, priorStepStateBindings = new Map(), joinFieldValues = new Set()) {
4010
4097
  if (parsedBody === null || typeof parsedBody !== "object") {
4011
4098
  return template;
4012
4099
  }
4100
+ const unconditionalExcludeValues = new Set(joinFieldValues);
4101
+ const restrictedExcludeSourceByValue = new Map();
4102
+ for (const [value, binding] of priorStepStateBindings) {
4103
+ if (binding.restricted)
4104
+ restrictedExcludeSourceByValue.set(value, binding.sourceName);
4105
+ else
4106
+ unconditionalExcludeValues.add(value);
4107
+ }
4013
4108
  // A cruise-line-style multi-room/multi-guest search body commonly batches
4014
4109
  // per-element criteria as a top-level JSON ARRAY rather than a single
4015
4110
  // object. Walk each element in textual order (via the growing searchFrom
@@ -4022,13 +4117,13 @@ function applyStructuredValuePayloadSubstitutions(template, parsedBody, outStruc
4022
4117
  for (const element of parsedBody) {
4023
4118
  if (element === null || typeof element !== "object" || Array.isArray(element))
4024
4119
  continue;
4025
- const { result: nextResult, nextSearchFrom } = applyStructuredValuePayloadSubstitutionsForObjectBody(result, element, outStructuredKeys, priorStepStateValues, cursor);
4120
+ const { result: nextResult, nextSearchFrom } = applyStructuredValuePayloadSubstitutionsForObjectBody(result, element, outStructuredKeys, unconditionalExcludeValues, restrictedExcludeSourceByValue, cursor);
4026
4121
  result = nextResult;
4027
4122
  cursor = nextSearchFrom;
4028
4123
  }
4029
4124
  return result;
4030
4125
  }
4031
- const { result } = applyStructuredValuePayloadSubstitutionsForObjectBody(template, parsedBody, outStructuredKeys, priorStepStateValues, 0);
4126
+ const { result } = applyStructuredValuePayloadSubstitutionsForObjectBody(template, parsedBody, outStructuredKeys, unconditionalExcludeValues, restrictedExcludeSourceByValue, 0);
4032
4127
  return result;
4033
4128
  }
4034
4129
  /** Maximum length to guard against indexing massive blobs (HTML fragments,
@@ -5742,13 +5837,14 @@ function applyVolatileFieldSubstitutions(template, parsedBody, shieldedUuids, bo
5742
5837
  return result;
5743
5838
  }
5744
5839
  /**
5745
- * Collects captured string leaves that survived every binding/generation pass as
5746
- * still-literal — the values a reviewer must look at because they couldn't be
5747
- * traced to a payload field, a generator, or a schema anchor. Returns the JSON
5748
- * key names (deduped, in first-seen order) so the emitter can prepend a single
5749
- * `// TODO: unbound captured literal` marker; it never mutates the body, so the
5750
- * file still compiles. Short values (< {@link MIN_STATE_VALUE_LENGTH}) are
5751
- * skipped — they are the legitimately-constant enum-like fields.
5840
+ * Collects captured string leaves AND whole array/object values that survived
5841
+ * every binding/generation pass as still-literal — the values a reviewer must
5842
+ * look at because they couldn't be traced to a payload field, a generator, or
5843
+ * a schema anchor. Returns the JSON key names (deduped, in first-seen order)
5844
+ * so the emitter can prepend a single `// TODO: unbound captured literal`
5845
+ * marker; it never mutates the body, so the file still compiles. Short string
5846
+ * values (< {@link MIN_STATE_VALUE_LENGTH}) are skipped — they are the
5847
+ * legitimately-constant enum-like fields.
5752
5848
  */
5753
5849
  function collectUnboundLiterals(finalTemplate, parsedBody, shieldedUuids) {
5754
5850
  const unbound = [];
@@ -5767,6 +5863,20 @@ function collectUnboundLiterals(finalTemplate, parsedBody, shieldedUuids) {
5767
5863
  unbound.push(key);
5768
5864
  }
5769
5865
  }
5866
+ if (parsedBody !== null && typeof parsedBody === "object") {
5867
+ for (const [key, value] of Object.entries(parsedBody)) {
5868
+ if (value === null || typeof value !== "object")
5869
+ continue;
5870
+ if (seen.has(key))
5871
+ continue;
5872
+ // Whole array/object value never got threaded as a unit — ${JSON.stringify(...)}
5873
+ // never replaced it, so it still sits verbatim in the emitted template.
5874
+ if (finalTemplate.includes(JSON.stringify(value))) {
5875
+ seen.add(key);
5876
+ unbound.push(key);
5877
+ }
5878
+ }
5879
+ }
5770
5880
  return unbound;
5771
5881
  }
5772
5882
  /** Builds the multi-step `executeHttp` body as a single template-literal string.
@@ -6280,10 +6390,7 @@ flowSteps = [], vocabulary = undefined) {
6280
6390
  // dependent-drill-down join field (e.g. a bulk `{"tokens":[...]}` lookup)
6281
6391
  // must stay reachable for state threading, not get frozen as caller data.
6282
6392
  const rawBodyWithStructuredSubs = parsedBody !== null
6283
- ? applyStructuredValuePayloadSubstitutions(rawBodyWithFacetSplices, parsedBody, outStructuredKeys, new Set([
6284
- ...deriveStateVarByValue(prior, cap).keys(),
6285
- ...(joinFieldValuesByStep.get(i) ?? []),
6286
- ]))
6393
+ ? applyStructuredValuePayloadSubstitutions(rawBodyWithFacetSplices, parsedBody, outStructuredKeys, deriveStateVarByValue(prior, cap), joinFieldValuesByStep.get(i) ?? new Set())
6287
6394
  : rawBodyWithFormSubs;
6288
6395
  // Whole-value caller coordinates bind here — after structured subs, BEFORE
6289
6396
  // state threading — so a composite coordinate (a jobLocation or jobSeqNo
@@ -11491,10 +11598,22 @@ function computeFlowPayloadFieldNames(flowSteps, vocabulary, env) {
11491
11598
  for (const step of flowSteps) {
11492
11599
  const isObj = typeof step !== "string";
11493
11600
  const instruction = isObj ? step.step : step;
11494
- // navigateTo/emailStep/password-token steps bypass field resolution
11495
- // entirely — see emitBrowserFlowTs's stepLiterals pass for why.
11496
- if (isObj && step.navigateTo !== undefined)
11601
+ // A navigateTo step's own instruction text bypasses field resolution —
11602
+ // it's a captured page.goto, not a prose instruction, so there is no
11603
+ // label/quoted-constant to correlate (see emitBrowserFlowTs's stepLiterals
11604
+ // pass). An explicit `payloadField` annotation is a different thing: it is
11605
+ // the author directly naming the facet the navigation target encodes, and
11606
+ // resolveStepPayloadField's own `explicit` short-circuit would honor it
11607
+ // unconditionally — so it must still be registered here, or every
11608
+ // downstream consumer of this accumulator (REST facet-splice, schema
11609
+ // declaration) silently drops the facet the author explicitly declared.
11610
+ if (isObj && step.navigateTo !== undefined) {
11611
+ if (step.payloadField) {
11612
+ payloadFieldNames.add(step.payloadField);
11613
+ registerFieldOptionality(step.payloadField, step.optional === true);
11614
+ }
11497
11615
  continue;
11616
+ }
11498
11617
  if (isObj && step.emailStep === true)
11499
11618
  continue;
11500
11619
  if (instruction.includes(RECON_PASSWORD_TOKEN))