@enricai/barnacle 1.12.8 → 1.12.10

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.
@@ -506,14 +506,14 @@ const IGNORE_REQUEST_HEADERS = new Set([
506
506
  */
507
507
  function deriveRequestHeaders(captures, replays, baseUrl, submitPatterns = null) {
508
508
  const successfulUrls = new Set(replays.filter((r) => r.success).map((r) => endpointKey(r.url)));
509
- // Prefer ACTION captures (non-GET 2xx to baseUrl host, non-telemetry) as
510
- // the authoritative header source. Replay-matched static-asset GETs lack
511
- // the API-specific headers that REST endpoints require, so falling back
512
- // to those produces a degenerate baseline-only header set. When action
513
- // captures exist (multi-step submission flows), use them. For sites
514
- // where the flow is a single REST call (no detectable action sequence),
515
- // fall back to the replay-matched captures.
516
- const actionCaptures = extractActionSequence(captures, baseUrl, submitPatterns).map((a) => a.capture);
509
+ // Prefer ACTION captures (non-GET 2xx, non-noise) as the authoritative
510
+ // header source. Replay-matched static-asset GETs lack the API-specific
511
+ // headers that REST endpoints require, so falling back to those produces
512
+ // a degenerate baseline-only header set. When action captures exist
513
+ // (multi-step submission flows), use them. For sites where the flow is a
514
+ // single REST call (no detectable action sequence), fall back to the
515
+ // replay-matched captures.
516
+ const actionCaptures = extractActionSequence(captures, submitPatterns).map((a) => a.capture);
517
517
  const replayMatchedCaptures = captures.filter((c) => successfulUrls.has(endpointKey(c.url)));
518
518
  const relevantCaptures = actionCaptures.length > 0 ? actionCaptures : replayMatchedCaptures;
519
519
  const counts = new Map();
@@ -707,26 +707,23 @@ function resolveManifestActionSequence(runRoot, captures) {
707
707
  }
708
708
  /**
709
709
  * Extracts the ordered sequence of meaningful POSTs that represent the
710
- * transactional flow: same-host 2xx POSTs, minus telemetry and error-reporting
711
- * sinks. Assets need no filter of their own — they arrive as GETs.
710
+ * transactional flow: non-noise 2xx POSTs, minus telemetry and error-reporting
711
+ * sinks. Assets need no filter of their own — they arrive as GETs. Host is
712
+ * NOT a filter criterion — a multi-step submission routinely bounces to a
713
+ * different host mid-flow (an account-creation redirect, a tenant API
714
+ * subdomain distinct from the landing page's host), so `isNoiseUrl` alone
715
+ * decides what counts as site traffic vs. third-party noise.
712
716
  *
713
717
  * When the flow declares submit patterns, only POSTs matching them survive —
714
718
  * this isolates the submission from same-origin page chrome (bootstrap, chatbot,
715
719
  * JWT refresh, reference-lookup) that a browser fires incidentally. Absent
716
- * patterns preserve the host/noise heuristic exactly.
720
+ * patterns preserve the noise heuristic exactly.
717
721
  *
718
722
  * Exported for tests: this predicate decides what a generated plugin will POST
719
723
  * at a live site, and it is the only gate between a browser's incidental
720
724
  * chatter and the emitted hot path.
721
725
  */
722
- function extractActionSequence(captures, baseUrl, submitPatterns = null) {
723
- let host;
724
- try {
725
- host = new URL(baseUrl).host;
726
- }
727
- catch {
728
- host = "";
729
- }
726
+ function extractActionSequence(captures, submitPatterns = null) {
730
727
  const matchesSubmit = compileSubmitMatcher(submitPatterns);
731
728
  return captures
732
729
  .map((capture, index) => ({ capture, index }))
@@ -735,15 +732,6 @@ function extractActionSequence(captures, baseUrl, submitPatterns = null) {
735
732
  return false;
736
733
  if (capture.status < 200 || capture.status >= 300)
737
734
  return false;
738
- let captureHost;
739
- try {
740
- captureHost = new URL(capture.url).host;
741
- }
742
- catch {
743
- return false;
744
- }
745
- if (captureHost !== host)
746
- return false;
747
735
  if ((0, capture_filters_1.isNoiseUrl)(capture.url))
748
736
  return false;
749
737
  if (!matchesSubmit(capture))
@@ -759,19 +747,13 @@ function extractActionSequence(captures, baseUrl, submitPatterns = null) {
759
747
  * make up a transactional flow (`UpsertSavedApplication`, `SubmitForm`, ...)
760
748
  * — and drops `query` operations, which are read/bootstrap calls (e.g. a
761
749
  * page-load `ListForms`) that carry no state-threading value and are exactly
762
- * what let a chronologically-first fallback pick an unrelated query.
750
+ * what let a chronologically-first fallback pick an unrelated query. Host is
751
+ * NOT a filter criterion, matching {@link extractActionSequence}.
763
752
  *
764
753
  * Exported for tests: this predicate decides what a generated GraphQL plugin
765
754
  * will send at a live site.
766
755
  */
767
- function extractGraphQLActionSequence(captures, baseUrl, submitPatterns = null) {
768
- let host;
769
- try {
770
- host = new URL(baseUrl).host;
771
- }
772
- catch {
773
- host = "";
774
- }
756
+ function extractGraphQLActionSequence(captures, submitPatterns = null) {
775
757
  const matchesSubmit = compileSubmitMatcher(submitPatterns);
776
758
  return captures
777
759
  .map((capture, index) => ({ capture, index }))
@@ -780,15 +762,6 @@ function extractGraphQLActionSequence(captures, baseUrl, submitPatterns = null)
780
762
  return false;
781
763
  if (capture.status < 200 || capture.status >= 300)
782
764
  return false;
783
- let captureHost;
784
- try {
785
- captureHost = new URL(capture.url).host;
786
- }
787
- catch {
788
- return false;
789
- }
790
- if (captureHost !== host)
791
- return false;
792
765
  if ((0, capture_filters_1.isNoiseUrl)(capture.url))
793
766
  return false;
794
767
  if (!matchesSubmit(capture))
@@ -3298,20 +3271,18 @@ function emitContractTs(opts) {
3298
3271
  // invocation, so heterogeneous per-call shapes are each checked against
3299
3272
  // their own inferred schema regardless of what this client-level schema is.
3300
3273
  //
3301
- // For multi-step flows this stays z.unknown(), not an unfinished schema: a
3302
- // submission flow's terminal shape is the plugin's OWN contract with its
3303
- // caller (e.g. { verified: boolean }), a field that appears in zero
3304
- // captured responses. Inferring a schema from the captures would emit the
3305
- // wrong shape with false confidence. z.unknown() plus the generated
3306
- // `[ ] Narrow ResponseSchema` checklist item is the intended hand-off to
3307
- // the plugin author, who alone knows that contract — narrowing it only
3308
- // changes what executeHttp promises its caller, and is now safe to do
3309
- // without affecting per-call validation. Single-endpoint plugins keep the
3310
- // inferred schema, since there both roles (client default and sole call)
3311
- // coincide.
3312
- // Browser-flow-only plugins have no HTTP call to infer a shape from either
3313
- // — same z.unknown() treatment as the multi-step case, for the same reason.
3314
- const responseSchemaExpr = multiStepBody || omitExecuteHttp ? `z.unknown()` : inferZodSchema(responseBody);
3274
+ // For multi-step flows, `responseBody` here is already
3275
+ // `selectEffectiveResponseBody`'s pick the SAME call `selectReturnAction`
3276
+ // returns from `executeHttp` (`return { data: r9 }` below, r9 being that
3277
+ // call's own already-validated result). Inferring the client-level schema
3278
+ // from it is therefore not a guess about a field the captures never showed:
3279
+ // it's the literal shape of the value the client hands back, captured by
3280
+ // its own success response (e.g. a `valid`/`errors`-shaped terminal body).
3281
+ // Single-endpoint plugins keep the same inferred-schema treatment, since
3282
+ // there both roles (client default and sole call) coincide.
3283
+ // Browser-flow-only plugins have no HTTP call to infer a shape from —
3284
+ // z.unknown() there is the honest gap, not a narrowing shortcut.
3285
+ const responseSchemaExpr = omitExecuteHttp ? `z.unknown()` : inferZodSchema(responseBody);
3315
3286
  // Multi-step flows that include a multipart upload need the binary asset
3316
3287
  // on the payload. ApplicantContactSchema (via ApplicantResumeSchema) already
3317
3288
  // declares Resume/ResumeContentType/ResumeFilename, so submission flows
@@ -4178,9 +4149,7 @@ async function main() {
4178
4149
  // Hoisted so both the primary-operation gate below and rawActionCaptures
4179
4150
  // (further down) read the same computed sequence instead of calling the
4180
4151
  // extractor twice.
4181
- const graphqlActionSequence = gql
4182
- ? extractGraphQLActionSequence(captures, baseUrl, submitPatterns)
4183
- : [];
4152
+ const graphqlActionSequence = gql ? extractGraphQLActionSequence(captures, submitPatterns) : [];
4184
4153
  const isReadOnlyFlow = !flowSteps.some((step) => typeof step !== "string" && step.submitStep === true);
4185
4154
  const primaryGraphQLOperation = gql && isReadOnlyFlow && graphqlActionSequence.length === 0
4186
4155
  ? selectPrimaryGraphQLOperation(captures, flowSteps, vocabulary)
@@ -4201,7 +4170,7 @@ async function main() {
4201
4170
  // heuristic extraction finds.
4202
4171
  const patternedHeuristicActionCaptures = gql
4203
4172
  ? graphqlActionSequence
4204
- : collapseRedundantPatches(extractActionSequence(captures, baseUrl, submitPatterns));
4173
+ : collapseRedundantPatches(extractActionSequence(captures, submitPatterns));
4205
4174
  // The same undercount hazard applies one layer below the manifest: a
4206
4175
  // flow-declared submitEndpointPattern that matches only one section's URL
4207
4176
  // (the natural way to describe "the button that finishes the wizard")
@@ -4214,8 +4183,8 @@ async function main() {
4214
4183
  const unfilteredHeuristicActionCaptures = submitPatterns.endpoint === null && submitPatterns.body === null
4215
4184
  ? patternedHeuristicActionCaptures
4216
4185
  : gql
4217
- ? extractGraphQLActionSequence(captures, baseUrl, null)
4218
- : collapseRedundantPatches(extractActionSequence(captures, baseUrl, null));
4186
+ ? extractGraphQLActionSequence(captures, null)
4187
+ : collapseRedundantPatches(extractActionSequence(captures, null));
4219
4188
  const patternUndercounts = patternedHeuristicActionCaptures.length < unfilteredHeuristicActionCaptures.length;
4220
4189
  if (patternUndercounts) {
4221
4190
  logger.info(`submission selection: ignoring submitEndpointPattern/submitBodyPattern (${patternedHeuristicActionCaptures.length} capture(s)) as an undercount of the unfiltered heuristic action sequence (${unfilteredHeuristicActionCaptures.length} capture(s))`);
@@ -4238,16 +4207,16 @@ async function main() {
4238
4207
  // The declared submitEndpointPattern's own under-match: unlike manifestUndercounts
4239
4208
  // (which compares an authoritative submit-manifest.json against the heuristic
4240
4209
  // sequence), this compares the pattern-filtered heuristic sequence against the SAME
4241
- // captures with no submitPatterns filter at all — the true raw same-host non-GET 2xx
4210
+ // captures with no submitPatterns filter at all — the true raw non-GET 2xx non-noise
4242
4211
  // action set. A pattern that matches conspicuously fewer calls than that raw set is a
4243
4212
  // detection failure (the pattern is too narrow), not evidence the flow is read-only,
4244
4213
  // and per the no-silent-fallback rule must not be allowed to quietly collapse into the
4245
4214
  // generic single-endpoint {query} template below.
4246
- const rawSameHostActionCaptures = submitEndpointPattern === null
4215
+ const rawUnfilteredActionCaptures = submitEndpointPattern === null
4247
4216
  ? null
4248
4217
  : gql
4249
- ? extractGraphQLActionSequence(captures, baseUrl, null)
4250
- : collapseRedundantPatches(extractActionSequence(captures, baseUrl, null));
4218
+ ? extractGraphQLActionSequence(captures, null)
4219
+ : collapseRedundantPatches(extractActionSequence(captures, null));
4251
4220
  // Form-schema detection runs BEFORE state-indexing so the field-id/option-id
4252
4221
  // UUIDs can be shielded from indexing — those UUIDs are stable schema
4253
4222
  // anchors that T2/T3 substitution depends on remaining literal in body
@@ -4307,10 +4276,10 @@ async function main() {
4307
4276
  // excluded real action captures the unfiltered raw set still has, emitting the generic
4308
4277
  // {query} template would misrepresent a genuine multi-call flow as read-only. Exit
4309
4278
  // rather than degrade quietly, per the no-defensive/no-silent-fallback rule.
4310
- if (rawSameHostActionCaptures !== null &&
4279
+ if (rawUnfilteredActionCaptures !== null &&
4311
4280
  !isSubmissionFlow &&
4312
- rawSameHostActionCaptures.length > heuristicActionCaptures.length) {
4313
- logger.error(`ERROR declared submitEndpointPattern ${JSON.stringify(submitEndpointPattern)} matched only ${heuristicActionCaptures.length} of ${rawSameHostActionCaptures.length} raw same-host non-GET 2xx action capture(s) — this under-match looks like a detection failure, not a read-only flow; refusing to fall back to the generic single-endpoint {query} template. Fix submitEndpointPattern in recon-flow.json to cover the real submission calls.`);
4281
+ rawUnfilteredActionCaptures.length > heuristicActionCaptures.length) {
4282
+ logger.error(`ERROR declared submitEndpointPattern ${JSON.stringify(submitEndpointPattern)} matched only ${heuristicActionCaptures.length} of ${rawUnfilteredActionCaptures.length} raw non-GET 2xx non-noise action capture(s) — this under-match looks like a detection failure, not a read-only flow; refusing to fall back to the generic single-endpoint {query} template. Fix submitEndpointPattern in recon-flow.json to cover the real submission calls.`);
4314
4283
  process.exit(1);
4315
4284
  }
4316
4285
  // Diagnostic for the FAILURE-3 shape (a flowless recon capture): a "submission flow"