@apifuse/provider-sdk 2.2.0-beta.46 → 2.2.0-beta.47
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.
- package/CHANGELOG.md +4 -0
- package/bin/apifuse-submit-check.ts +239 -9
- package/dist/runtime/resolver-shared.d.ts +12 -1
- package/dist/runtime/resolver.js +2 -2
- package/dist/server/serve-implementation.js +5 -3
- package/package.json +1 -1
- package/src/runtime/resolver-shared.ts +17 -1
- package/src/runtime/resolver.ts +7 -2
- package/src/server/serve-implementation.ts +13 -4
package/CHANGELOG.md
CHANGED
|
@@ -4312,6 +4312,7 @@ function scoreHealthCoverage(provider: ProviderDefinition): SubmitCheck {
|
|
|
4312
4312
|
const operations = Object.entries(provider.operations);
|
|
4313
4313
|
const missing: string[] = [];
|
|
4314
4314
|
const vacuous: string[] = [];
|
|
4315
|
+
const evidenceFreeScenario: string[] = [];
|
|
4315
4316
|
const placeholder: string[] = [];
|
|
4316
4317
|
const unsupported: string[] = [];
|
|
4317
4318
|
const generatedStarter: string[] = [];
|
|
@@ -4323,8 +4324,13 @@ function scoreHealthCoverage(provider: ProviderDefinition): SubmitCheck {
|
|
|
4323
4324
|
missing.push(operationId);
|
|
4324
4325
|
continue;
|
|
4325
4326
|
}
|
|
4326
|
-
if (hasCheck && !hasUnsupported
|
|
4327
|
-
|
|
4327
|
+
if (hasCheck && !hasUnsupported) {
|
|
4328
|
+
const vacuousClass = vacuousHealthCaseClass(operation.healthCheck);
|
|
4329
|
+
if (vacuousClass === "empty-assertions") {
|
|
4330
|
+
vacuous.push(operationId);
|
|
4331
|
+
} else if (vacuousClass === "evidence-free-scenario") {
|
|
4332
|
+
evidenceFreeScenario.push(operationId);
|
|
4333
|
+
}
|
|
4328
4334
|
}
|
|
4329
4335
|
if (hasUnsupported) {
|
|
4330
4336
|
const reason = operation.healthCheckUnsupported?.reason ?? "";
|
|
@@ -4351,14 +4357,36 @@ function scoreHealthCoverage(provider: ProviderDefinition): SubmitCheck {
|
|
|
4351
4357
|
);
|
|
4352
4358
|
}
|
|
4353
4359
|
|
|
4354
|
-
if (vacuous.length > 0) {
|
|
4360
|
+
if (vacuous.length > 0 || evidenceFreeScenario.length > 0) {
|
|
4361
|
+
const remediationParts: string[] = [];
|
|
4362
|
+
if (vacuous.length > 0) {
|
|
4363
|
+
remediationParts.push(
|
|
4364
|
+
`healthCheck.assertions for ${vacuous.join(", ")} is empty — assert on status and response shape (e.g. throw or return {status:'degraded'} when the upstream contract breaks), or declare healthCheckUnsupported with a specific reason if the operation genuinely cannot be probed.`,
|
|
4365
|
+
);
|
|
4366
|
+
}
|
|
4367
|
+
if (evidenceFreeScenario.length > 0) {
|
|
4368
|
+
remediationParts.push(
|
|
4369
|
+
`healthCheck scenario for ${evidenceFreeScenario.join(", ")} asserts nothing beyond transport — add an assert step whose expression reads the probe response (a field under data, an extracted value, or a quantifier over response rows), not just status_2xx or a bare response-envelope check.`,
|
|
4370
|
+
);
|
|
4371
|
+
}
|
|
4372
|
+
const message =
|
|
4373
|
+
evidenceFreeScenario.length === 0
|
|
4374
|
+
? "One or more operations have healthCheck cases with empty assertions."
|
|
4375
|
+
: vacuous.length === 0
|
|
4376
|
+
? "One or more operations have healthCheck scenarios without response evidence."
|
|
4377
|
+
: "One or more operations have healthCheck cases with empty assertions or evidence-free scenarios.";
|
|
4355
4378
|
return blocker(
|
|
4356
4379
|
"health-coverage",
|
|
4357
4380
|
"health",
|
|
4358
|
-
|
|
4359
|
-
|
|
4381
|
+
message,
|
|
4382
|
+
remediationParts.join(" "),
|
|
4360
4383
|
CATEGORY_MAX_POINTS.health,
|
|
4361
|
-
|
|
4384
|
+
[
|
|
4385
|
+
...vacuous.map((operationId) => `${operationId}: empty healthCheck.assertions`),
|
|
4386
|
+
...evidenceFreeScenario.map(
|
|
4387
|
+
(operationId) => `${operationId}: healthCheck scenario asserts nothing beyond transport`,
|
|
4388
|
+
),
|
|
4389
|
+
],
|
|
4362
4390
|
);
|
|
4363
4391
|
}
|
|
4364
4392
|
|
|
@@ -4413,14 +4441,216 @@ function scoreHealthCoverage(provider: ProviderDefinition): SubmitCheck {
|
|
|
4413
4441
|
);
|
|
4414
4442
|
}
|
|
4415
4443
|
|
|
4416
|
-
|
|
4444
|
+
type VacuousHealthCaseClass = "empty-assertions" | "evidence-free-scenario";
|
|
4445
|
+
|
|
4446
|
+
/**
|
|
4447
|
+
* Classify an operation whose every health case is vacuous. Returns undefined
|
|
4448
|
+
* when at least one case carries real coverage: an imperative case with a
|
|
4449
|
+
* non-vacuous assertions function, or a declarative case whose scenario reads
|
|
4450
|
+
* the probe response. When all cases are vacuous, the class picks the blocker
|
|
4451
|
+
* wording — "empty-assertions" whenever any vacuous case is imperative (the
|
|
4452
|
+
* pre-scenario behavior, kept byte-identical), "evidence-free-scenario" when
|
|
4453
|
+
* the suite is scenario-only.
|
|
4454
|
+
*/
|
|
4455
|
+
function vacuousHealthCaseClass(
|
|
4417
4456
|
healthCheck: ProviderDefinition["operations"][string]["healthCheck"],
|
|
4418
|
-
):
|
|
4457
|
+
): VacuousHealthCaseClass | undefined {
|
|
4419
4458
|
const cases = healthCheck?.cases;
|
|
4420
4459
|
if (!Array.isArray(cases) || cases.length === 0) {
|
|
4460
|
+
return "empty-assertions";
|
|
4461
|
+
}
|
|
4462
|
+
let sawImperativeCase = false;
|
|
4463
|
+
for (const healthCase of cases) {
|
|
4464
|
+
if (healthCase?.scenario !== undefined) {
|
|
4465
|
+
if (scenarioCarriesResponseEvidence(healthCase.scenario)) {
|
|
4466
|
+
return undefined;
|
|
4467
|
+
}
|
|
4468
|
+
continue;
|
|
4469
|
+
}
|
|
4470
|
+
sawImperativeCase = true;
|
|
4471
|
+
if (!isVacuousAssertionFunction(healthCase?.assertions)) {
|
|
4472
|
+
return undefined;
|
|
4473
|
+
}
|
|
4474
|
+
}
|
|
4475
|
+
return sawImperativeCase ? "empty-assertions" : "evidence-free-scenario";
|
|
4476
|
+
}
|
|
4477
|
+
|
|
4478
|
+
/**
|
|
4479
|
+
* A declarative case (`scenario`) carries real coverage when at least one
|
|
4480
|
+
* `assert` step reads the probe response beyond the transport envelope. The
|
|
4481
|
+
* SDK's own definitions drive this judgement:
|
|
4482
|
+
*
|
|
4483
|
+
* - The assert step is the scenario's only verdict-bearing construct
|
|
4484
|
+
* (`AssertResult.passed`). A guard can only attribute
|
|
4485
|
+
* degraded/expected_absence and stop the run (an escape hatch, not a health
|
|
4486
|
+
* verdict), and an operation step does not intrinsically fail on a non-2xx
|
|
4487
|
+
* response — that is what the explicit `status_2xx` predicate exists for. So
|
|
4488
|
+
* a scenario without a semantic assert step proves nothing beyond the
|
|
4489
|
+
* operation merely executing, which is the same emptiness the imperative
|
|
4490
|
+
* vacuity check flags.
|
|
4491
|
+
* - Leaves that cannot observe upstream content carry no evidence: predicates
|
|
4492
|
+
* whose operands read nothing produced by a step (pure literals,
|
|
4493
|
+
* attempt/credential metadata, and the `status_code`/`request_id`/`kind`
|
|
4494
|
+
* envelope fields of a step result — this is what makes the legacy transport
|
|
4495
|
+
* pin `status_2xx` on `["status_code"]` non-evidence), predicates comparing
|
|
4496
|
+
* a reference against an identical copy of itself (`equals(x, x)` holds for
|
|
4497
|
+
* any response), and positive-polarity presence/type pins (`exists`,
|
|
4498
|
+
* `type_is`, `not(not_exists(...))`, zero-bound `array_length_gte`) on the
|
|
4499
|
+
* bare `data` root — the declarative spellings of the legacy transport-only
|
|
4500
|
+
* assertion (`status === 200` + `typeof data === "object"`), satisfied by
|
|
4501
|
+
* any well-formed response body. Polarity matters: `not(exists(data))`
|
|
4502
|
+
* ("this probe must return no body") is falsified by ordinary responses, so
|
|
4503
|
+
* the carve-out only applies where the pin appears un-negated.
|
|
4504
|
+
* - Everything else counts: paths under `data`, extract-step results,
|
|
4505
|
+
* `duration_ms` (parity with imperative `ctx.durationMs` reads),
|
|
4506
|
+
* `status_2xx` applied to a body path (envelope-style upstreams answer HTTP
|
|
4507
|
+
* 200 and carry the real code in the body), and quantifiers, which iterate
|
|
4508
|
+
* response rows by construction.
|
|
4509
|
+
*
|
|
4510
|
+
* Fail-open contract (same as isVacuousAssertionFunction): the provider module
|
|
4511
|
+
* already validated this scenario against HealthScenarioSchema in its own SDK
|
|
4512
|
+
* at import time, so any shape this walk does not recognize (unknown step
|
|
4513
|
+
* kind, expression kind, operator, or reference namespace) indicates a newer
|
|
4514
|
+
* grammar, not an empty probe — treat it as evidence rather than flag it.
|
|
4515
|
+
* The inverse direction is accepted and documented: a determined author can
|
|
4516
|
+
* still fabricate a read that never fails (a decorative deep path inside a
|
|
4517
|
+
* disjunction, a degenerate bound). That effort is comparable to writing the
|
|
4518
|
+
* real one-line assertion — the same accepted limitation as the imperative
|
|
4519
|
+
* check's decorative-ctx-read — and the live `--smoke` probe remains the
|
|
4520
|
+
* runtime defense.
|
|
4521
|
+
*/
|
|
4522
|
+
export function scenarioCarriesResponseEvidence(scenario: unknown): boolean {
|
|
4523
|
+
if (!isRecord(scenario) || !Array.isArray(scenario.steps)) {
|
|
4524
|
+
return true;
|
|
4525
|
+
}
|
|
4526
|
+
let sawUnknownStepKind = false;
|
|
4527
|
+
for (const step of scenario.steps) {
|
|
4528
|
+
if (!isRecord(step)) {
|
|
4529
|
+
sawUnknownStepKind = true;
|
|
4530
|
+
continue;
|
|
4531
|
+
}
|
|
4532
|
+
if (step.kind === "assert") {
|
|
4533
|
+
if (expressionReadsResponse(step.expression)) {
|
|
4534
|
+
return true;
|
|
4535
|
+
}
|
|
4536
|
+
continue;
|
|
4537
|
+
}
|
|
4538
|
+
if (step.kind !== "operation" && step.kind !== "extract" && step.kind !== "guard") {
|
|
4539
|
+
sawUnknownStepKind = true;
|
|
4540
|
+
}
|
|
4541
|
+
}
|
|
4542
|
+
return sawUnknownStepKind;
|
|
4543
|
+
}
|
|
4544
|
+
|
|
4545
|
+
/**
|
|
4546
|
+
* Predicate operators of the current scenario grammar. An operator outside
|
|
4547
|
+
* this set indicates a newer grammar and fails open (counts as evidence),
|
|
4548
|
+
* because a future operator may source the response through fields this walk
|
|
4549
|
+
* does not know about.
|
|
4550
|
+
*/
|
|
4551
|
+
const KNOWN_SCENARIO_PREDICATE_OPERATORS: ReadonlySet<string> = new Set([
|
|
4552
|
+
"exists",
|
|
4553
|
+
"not_exists",
|
|
4554
|
+
"non_empty",
|
|
4555
|
+
"is_true",
|
|
4556
|
+
"equals",
|
|
4557
|
+
"not_equals",
|
|
4558
|
+
"contains",
|
|
4559
|
+
"matches",
|
|
4560
|
+
"number_gt",
|
|
4561
|
+
"number_gte",
|
|
4562
|
+
"number_lt",
|
|
4563
|
+
"number_lte",
|
|
4564
|
+
"array_length_eq",
|
|
4565
|
+
"array_length_gte",
|
|
4566
|
+
"array_length_lte",
|
|
4567
|
+
"status_2xx",
|
|
4568
|
+
"type_is",
|
|
4569
|
+
]);
|
|
4570
|
+
|
|
4571
|
+
function expressionReadsResponse(expression: unknown, negationDepth = 0): boolean {
|
|
4572
|
+
if (!isRecord(expression)) {
|
|
4421
4573
|
return true;
|
|
4422
4574
|
}
|
|
4423
|
-
|
|
4575
|
+
if (expression.kind === "all" || expression.kind === "any") {
|
|
4576
|
+
return Array.isArray(expression.clauses)
|
|
4577
|
+
? expression.clauses.some((clause) => expressionReadsResponse(clause, negationDepth))
|
|
4578
|
+
: true;
|
|
4579
|
+
}
|
|
4580
|
+
if (expression.kind === "not") {
|
|
4581
|
+
return expressionReadsResponse(expression.clause, negationDepth + 1);
|
|
4582
|
+
}
|
|
4583
|
+
if (expression.kind === "quantifier") {
|
|
4584
|
+
return true;
|
|
4585
|
+
}
|
|
4586
|
+
if (expression.kind !== "predicate" || typeof expression.operator !== "string") {
|
|
4587
|
+
return true;
|
|
4588
|
+
}
|
|
4589
|
+
const operator = expression.operator;
|
|
4590
|
+
if (!KNOWN_SCENARIO_PREDICATE_OPERATORS.has(operator)) {
|
|
4591
|
+
return true;
|
|
4592
|
+
}
|
|
4593
|
+
const actualReads = operandReadsStepOutput(expression.actual);
|
|
4594
|
+
const expectedReads = operandReadsStepOutput(expression.expected);
|
|
4595
|
+
if (!actualReads && !expectedReads) {
|
|
4596
|
+
return false;
|
|
4597
|
+
}
|
|
4598
|
+
// A predicate comparing a reference against an identical copy of itself
|
|
4599
|
+
// (equals(x, x) and friends) holds or fails identically for every response,
|
|
4600
|
+
// so the reads discriminate nothing.
|
|
4601
|
+
if (
|
|
4602
|
+
actualReads &&
|
|
4603
|
+
expectedReads &&
|
|
4604
|
+
JSON.stringify(expression.actual) === JSON.stringify(expression.expected)
|
|
4605
|
+
) {
|
|
4606
|
+
return false;
|
|
4607
|
+
}
|
|
4608
|
+
// Presence/type pins on the whole `data` root are satisfied by any
|
|
4609
|
+
// well-formed JSON body — but only in positive polarity. Under an odd
|
|
4610
|
+
// number of `not` wrappers the same pin means "this probe must return no
|
|
4611
|
+
// body / a different shape", which ordinary responses falsify, so it is a
|
|
4612
|
+
// real (if unusual) constraint and stays evidence.
|
|
4613
|
+
const positivePolarity = negationDepth % 2 === 0;
|
|
4614
|
+
const bareEnvelopePin = positivePolarity
|
|
4615
|
+
? operator === "exists" ||
|
|
4616
|
+
operator === "type_is" ||
|
|
4617
|
+
(operator === "array_length_gte" && expression.expected === 0)
|
|
4618
|
+
: operator === "not_exists";
|
|
4619
|
+
if (bareEnvelopePin && !expectedReads && isBareResponseEnvelope(expression.actual)) {
|
|
4620
|
+
return false;
|
|
4621
|
+
}
|
|
4622
|
+
return true;
|
|
4623
|
+
}
|
|
4624
|
+
|
|
4625
|
+
function operandReadsStepOutput(operand: unknown): boolean {
|
|
4626
|
+
if (!isRecord(operand) || !isRecord(operand.ref)) {
|
|
4627
|
+
return false;
|
|
4628
|
+
}
|
|
4629
|
+
const reference = operand.ref;
|
|
4630
|
+
if (reference.namespace === "attempt" || reference.namespace === "credentials") {
|
|
4631
|
+
return false;
|
|
4632
|
+
}
|
|
4633
|
+
if (reference.namespace !== "steps") {
|
|
4634
|
+
return true;
|
|
4635
|
+
}
|
|
4636
|
+
if (!Array.isArray(reference.path) || reference.path.length === 0) {
|
|
4637
|
+
return true;
|
|
4638
|
+
}
|
|
4639
|
+
const head = reference.path[0];
|
|
4640
|
+
return head !== "status_code" && head !== "request_id" && head !== "kind";
|
|
4641
|
+
}
|
|
4642
|
+
|
|
4643
|
+
function isBareResponseEnvelope(operand: unknown): boolean {
|
|
4644
|
+
if (!isRecord(operand) || !isRecord(operand.ref)) {
|
|
4645
|
+
return false;
|
|
4646
|
+
}
|
|
4647
|
+
const reference = operand.ref;
|
|
4648
|
+
return (
|
|
4649
|
+
reference.namespace === "steps" &&
|
|
4650
|
+
Array.isArray(reference.path) &&
|
|
4651
|
+
reference.path.length === 1 &&
|
|
4652
|
+
reference.path[0] === "data"
|
|
4653
|
+
);
|
|
4424
4654
|
}
|
|
4425
4655
|
|
|
4426
4656
|
function isVacuousAssertionFunction(assertions: unknown): boolean {
|
|
@@ -1,3 +1,14 @@
|
|
|
1
|
-
import type { ResolverContext } from "../types.js";
|
|
1
|
+
import type { ChallengeSolution, ProviderChallenge, ResolverContext } from "../types.js";
|
|
2
|
+
import type { TraceRecorder } from "./trace.js";
|
|
2
3
|
export declare const RESOLVER_INSTRUMENTATION_METADATA: unique symbol;
|
|
4
|
+
/**
|
|
5
|
+
* Internal solve surface. The instrumentation layer threads its recorder as a
|
|
6
|
+
* third argument so vendor-level spans (`resolver.vendor.*`) attach to the
|
|
7
|
+
* active trace. Every wrapper that re-exposes `solve` must forward the extra
|
|
8
|
+
* arguments; a wrapper typed against the public two-argument `ResolverContext`
|
|
9
|
+
* silently drops the recorder and vendor spans vanish.
|
|
10
|
+
*/
|
|
11
|
+
export type ResolverSolveWithRecorder = {
|
|
12
|
+
solve(challenge: ProviderChallenge, signal?: AbortSignal, traceRecorder?: TraceRecorder): Promise<ChallengeSolution>;
|
|
13
|
+
};
|
|
3
14
|
export declare function createUnsupportedResolverClient(reason?: string): ResolverContext;
|
package/dist/runtime/resolver.js
CHANGED
|
@@ -682,8 +682,8 @@ export function bindResolverSignal(resolver, defaultSignal) {
|
|
|
682
682
|
if (!defaultSignal)
|
|
683
683
|
return resolver;
|
|
684
684
|
const boundResolver = {
|
|
685
|
-
solve(challenge, signal = defaultSignal) {
|
|
686
|
-
return resolver.solve(challenge, signal);
|
|
685
|
+
solve(challenge, signal = defaultSignal, traceRecorder) {
|
|
686
|
+
return resolver.solve(challenge, signal, traceRecorder);
|
|
687
687
|
},
|
|
688
688
|
};
|
|
689
689
|
if (resolverCaches.has(resolver)) {
|
|
@@ -24,7 +24,7 @@ import { getProviderBaseUrl } from "../runtime/provider.js";
|
|
|
24
24
|
import { createOcrClientFromEnv } from "../runtime/ocr.js";
|
|
25
25
|
import { PROXY_AUTH_IP_DENIED_CODE, PROXY_EDGE_AUTH_REJECTED_CODE, PROXY_POOL_EXHAUSTED_CODE, } from "../runtime/proxy-errors.js";
|
|
26
26
|
import { PROVIDER_TELEMETRY_HEADER, ProxyTelemetryCollector, } from "../runtime/proxy-telemetry.js";
|
|
27
|
-
import { createUnsupportedResolverClient } from "../runtime/resolver-shared.js";
|
|
27
|
+
import { createUnsupportedResolverClient, } from "../runtime/resolver-shared.js";
|
|
28
28
|
import { assertRequiredSecretsPresent, listMissingRequiredSecrets, MISSING_SECRET_CODE, } from "../runtime/secrets.js";
|
|
29
29
|
import { createProviderRuntimeStateFromEnv, createUnsupportedProviderRuntimeState, } from "../runtime/state.js";
|
|
30
30
|
import { StealthCookieJar } from "../runtime/stealth-cookies.js";
|
|
@@ -291,8 +291,10 @@ function bindResolverSignalWithoutRuntime(resolver, defaultSignal) {
|
|
|
291
291
|
if (!defaultSignal)
|
|
292
292
|
return resolver;
|
|
293
293
|
return {
|
|
294
|
-
solve(challenge, signal = defaultSignal) {
|
|
295
|
-
|
|
294
|
+
solve(challenge, signal = defaultSignal, traceRecorder) {
|
|
295
|
+
// Forward the instrumentation trace recorder so resolver.vendor.* spans
|
|
296
|
+
// survive this wrapper. See ResolverSolveWithRecorder in resolver-shared.ts.
|
|
297
|
+
return resolver.solve(challenge, signal, traceRecorder);
|
|
296
298
|
},
|
|
297
299
|
};
|
|
298
300
|
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,26 @@
|
|
|
1
1
|
import { ProviderError } from "../errors.js";
|
|
2
|
-
import type { ResolverContext } from "../types.js";
|
|
2
|
+
import type { ChallengeSolution, ProviderChallenge, ResolverContext } from "../types.js";
|
|
3
|
+
import type { TraceRecorder } from "./trace.js";
|
|
3
4
|
|
|
4
5
|
export const RESOLVER_INSTRUMENTATION_METADATA = Symbol.for(
|
|
5
6
|
"@apifuse/provider-sdk/runtime/resolver-instrumentation-metadata",
|
|
6
7
|
);
|
|
7
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Internal solve surface. The instrumentation layer threads its recorder as a
|
|
11
|
+
* third argument so vendor-level spans (`resolver.vendor.*`) attach to the
|
|
12
|
+
* active trace. Every wrapper that re-exposes `solve` must forward the extra
|
|
13
|
+
* arguments; a wrapper typed against the public two-argument `ResolverContext`
|
|
14
|
+
* silently drops the recorder and vendor spans vanish.
|
|
15
|
+
*/
|
|
16
|
+
export type ResolverSolveWithRecorder = {
|
|
17
|
+
solve(
|
|
18
|
+
challenge: ProviderChallenge,
|
|
19
|
+
signal?: AbortSignal,
|
|
20
|
+
traceRecorder?: TraceRecorder,
|
|
21
|
+
): Promise<ChallengeSolution>;
|
|
22
|
+
};
|
|
23
|
+
|
|
8
24
|
export function createUnsupportedResolverClient(reason?: string): ResolverContext {
|
|
9
25
|
return {
|
|
10
26
|
async solve() {
|
package/src/runtime/resolver.ts
CHANGED
|
@@ -41,6 +41,7 @@ import {
|
|
|
41
41
|
import {
|
|
42
42
|
createUnsupportedResolverClient,
|
|
43
43
|
RESOLVER_INSTRUMENTATION_METADATA,
|
|
44
|
+
type ResolverSolveWithRecorder,
|
|
44
45
|
} from "./resolver-shared.js";
|
|
45
46
|
import {
|
|
46
47
|
APIFUSE__CDP_POOL__URL,
|
|
@@ -1022,8 +1023,12 @@ export function bindResolverSignal(
|
|
|
1022
1023
|
): ResolverContext {
|
|
1023
1024
|
if (!defaultSignal) return resolver;
|
|
1024
1025
|
const boundResolver: ResolverContext = {
|
|
1025
|
-
solve(challenge, signal = defaultSignal) {
|
|
1026
|
-
return resolver.solve(
|
|
1026
|
+
solve(challenge, signal = defaultSignal, traceRecorder?: TraceRecorder) {
|
|
1027
|
+
return (resolver as Partial<ResolverSolveWithRecorder> & ResolverContext).solve(
|
|
1028
|
+
challenge,
|
|
1029
|
+
signal,
|
|
1030
|
+
traceRecorder,
|
|
1031
|
+
);
|
|
1027
1032
|
},
|
|
1028
1033
|
};
|
|
1029
1034
|
if (resolverCaches.has(resolver)) {
|
|
@@ -64,7 +64,10 @@ import {
|
|
|
64
64
|
type ProxyTelemetryLogPayload,
|
|
65
65
|
} from "../runtime/proxy-telemetry.js";
|
|
66
66
|
import type * as ResolverRuntimeModule from "../runtime/resolver.js";
|
|
67
|
-
import {
|
|
67
|
+
import {
|
|
68
|
+
createUnsupportedResolverClient,
|
|
69
|
+
type ResolverSolveWithRecorder,
|
|
70
|
+
} from "../runtime/resolver-shared.js";
|
|
68
71
|
import {
|
|
69
72
|
assertRequiredSecretsPresent,
|
|
70
73
|
listMissingRequiredSecrets,
|
|
@@ -77,7 +80,7 @@ import {
|
|
|
77
80
|
import { StealthCookieJar } from "../runtime/stealth-cookies.js";
|
|
78
81
|
import type * as StealthRuntimeModule from "../runtime/stealth.js";
|
|
79
82
|
import { createSttClientFromEnv } from "../runtime/stt.js";
|
|
80
|
-
import { createTraceContext } from "../runtime/trace.js";
|
|
83
|
+
import { createTraceContext, type TraceRecorder } from "../runtime/trace.js";
|
|
81
84
|
import { resolveTraceConfigFromEnv } from "../runtime/trace-config.js";
|
|
82
85
|
import { parseSchema } from "../schema.js";
|
|
83
86
|
import {
|
|
@@ -493,8 +496,14 @@ function bindResolverSignalWithoutRuntime(
|
|
|
493
496
|
): ResolverContext {
|
|
494
497
|
if (!defaultSignal) return resolver;
|
|
495
498
|
return {
|
|
496
|
-
solve(challenge, signal = defaultSignal) {
|
|
497
|
-
|
|
499
|
+
solve(challenge, signal = defaultSignal, traceRecorder?: TraceRecorder) {
|
|
500
|
+
// Forward the instrumentation trace recorder so resolver.vendor.* spans
|
|
501
|
+
// survive this wrapper. See ResolverSolveWithRecorder in resolver-shared.ts.
|
|
502
|
+
return (resolver as Partial<ResolverSolveWithRecorder> & ResolverContext).solve(
|
|
503
|
+
challenge,
|
|
504
|
+
signal,
|
|
505
|
+
traceRecorder,
|
|
506
|
+
);
|
|
498
507
|
},
|
|
499
508
|
};
|
|
500
509
|
}
|