@workglow/test-contract 0.6.1
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/README.md +232 -0
- package/dist/ai-provider/assertions/capabilityHonesty.d.ts +7 -0
- package/dist/ai-provider/assertions/dispose.d.ts +7 -0
- package/dist/ai-provider/assertions/inferAdvertisesRegistered.d.ts +15 -0
- package/dist/ai-provider/assertions/inferServesInferred.d.ts +20 -0
- package/dist/ai-provider/assertions/pricingMatchesModality.d.ts +36 -0
- package/dist/ai-provider/assertions/registryCoverage.d.ts +13 -0
- package/dist/ai-provider/assertions/sessionReuse.d.ts +7 -0
- package/dist/ai-provider/assertions/signalHonoring.d.ts +7 -0
- package/dist/ai-provider/assertions/structuredGeneration.d.ts +7 -0
- package/dist/ai-provider/assertions/textGenerationSmoke.d.ts +7 -0
- package/dist/ai-provider/assertions/toolCallAccumulator.d.ts +7 -0
- package/dist/ai-provider/assertions/toolCallMultiTurn.d.ts +7 -0
- package/dist/ai-provider/assertions/usageNormalization.d.ts +61 -0
- package/dist/ai-provider/fixtures.d.ts +8 -0
- package/dist/ai-provider/runAiProviderConformance.d.ts +7 -0
- package/dist/ai-provider/types.d.ts +75 -0
- package/dist/ai-provider.d.ts +25 -0
- package/dist/ai-provider.js +876 -0
- package/dist/ai-provider.js.map +26 -0
- package/dist/browser-context/ConformanceMockContext.d.ts +54 -0
- package/dist/browser-context/assertions/ariaRoundTrip.d.ts +7 -0
- package/dist/browser-context/assertions/capabilityHonesty.d.ts +7 -0
- package/dist/browser-context/assertions/itExpectFail.d.ts +8 -0
- package/dist/browser-context/assertions/networkIntrospection.d.ts +7 -0
- package/dist/browser-context/assertions/tabsLifecycle.d.ts +7 -0
- package/dist/browser-context/fixtures.d.ts +8 -0
- package/dist/browser-context/runIBrowserContextConformance.d.ts +7 -0
- package/dist/browser-context/types.d.ts +48 -0
- package/dist/browser-context.d.ts +18 -0
- package/dist/browser-context.js +567 -0
- package/dist/browser-context.js.map +17 -0
- package/dist/creditExhaustedSkip.d.ts +39 -0
- package/dist/creditExhaustedSkip.js +163 -0
- package/dist/creditExhaustedSkip.js.map +10 -0
- package/dist/entitlement-profile/assertions/denialShape.d.ts +13 -0
- package/dist/entitlement-profile/assertions/dispose.d.ts +16 -0
- package/dist/entitlement-profile/assertions/hierarchyHonoring.d.ts +11 -0
- package/dist/entitlement-profile/assertions/optionalNeverDenied.d.ts +11 -0
- package/dist/entitlement-profile/assertions/requestEntitlementShape.d.ts +12 -0
- package/dist/entitlement-profile/assertions/resourceScoping.d.ts +16 -0
- package/dist/entitlement-profile/assertions/subscribeGrant.d.ts +11 -0
- package/dist/entitlement-profile/assertions/subscribeReload.d.ts +11 -0
- package/dist/entitlement-profile/assertions/subscribeRevocation.d.ts +12 -0
- package/dist/entitlement-profile/assertions/surfaceCoverage.d.ts +7 -0
- package/dist/entitlement-profile/assertions/unsubscribeIdempotent.d.ts +7 -0
- package/dist/entitlement-profile/fixtures.d.ts +21 -0
- package/dist/entitlement-profile/runEntitlementProfileConformance.d.ts +7 -0
- package/dist/entitlement-profile/types.d.ts +45 -0
- package/dist/entitlement-profile.d.ts +23 -0
- package/dist/entitlement-profile.js +436 -0
- package/dist/entitlement-profile.js.map +22 -0
- package/dist/human-connector/MockHumanConnector.d.ts +29 -0
- package/dist/human-connector/assertions/abort.d.ts +7 -0
- package/dist/human-connector/assertions/capabilityHonesty.d.ts +7 -0
- package/dist/human-connector/assertions/concurrentIsolation.d.ts +7 -0
- package/dist/human-connector/assertions/multiTurnFollowUp.d.ts +7 -0
- package/dist/human-connector/assertions/notifyDisplayFastResolve.d.ts +7 -0
- package/dist/human-connector/assertions/roundtrip.d.ts +7 -0
- package/dist/human-connector/fixtures.d.ts +8 -0
- package/dist/human-connector/runHumanConnectorConformance.d.ts +9 -0
- package/dist/human-connector/types.d.ts +91 -0
- package/dist/human-connector.d.ts +19 -0
- package/dist/human-connector.js +843 -0
- package/dist/human-connector.js.map +20 -0
- package/dist/itExpectFail.d.ts +15 -0
- package/dist/itExpectFail.js +180 -0
- package/dist/itExpectFail.js.map +11 -0
- package/dist/storage-migrations/assertions/appliesAndRecords.d.ts +7 -0
- package/dist/storage-migrations/assertions/concurrentRunsSerialize.d.ts +18 -0
- package/dist/storage-migrations/assertions/ensureBookkeepingIdempotent.d.ts +7 -0
- package/dist/storage-migrations/assertions/failedMigrationLeavesNoPartialSchema.d.ts +14 -0
- package/dist/storage-migrations/assertions/failedMigrationNotRecorded.d.ts +7 -0
- package/dist/storage-migrations/assertions/idempotentRun.d.ts +7 -0
- package/dist/storage-migrations/assertions/incrementalApplication.d.ts +7 -0
- package/dist/storage-migrations/runMigrationRunnerContract.d.ts +9 -0
- package/dist/storage-migrations/types.d.ts +78 -0
- package/dist/storage-migrations.d.ts +18 -0
- package/dist/storage-migrations.js +473 -0
- package/dist/storage-migrations.js.map +20 -0
- package/dist/tabular-migrations/assertions/addAndDropIndex.d.ts +7 -0
- package/dist/tabular-migrations/assertions/addColumn.d.ts +7 -0
- package/dist/tabular-migrations/assertions/backfill.d.ts +7 -0
- package/dist/tabular-migrations/assertions/dropColumn.d.ts +7 -0
- package/dist/tabular-migrations/assertions/failedMigrationNotRecorded.d.ts +7 -0
- package/dist/tabular-migrations/assertions/freshDbFastPath.d.ts +7 -0
- package/dist/tabular-migrations/assertions/incrementalApplication.d.ts +7 -0
- package/dist/tabular-migrations/assertions/renameColumn.d.ts +7 -0
- package/dist/tabular-migrations/runTabularMigrationContract.d.ts +8 -0
- package/dist/tabular-migrations/types.d.ts +28 -0
- package/dist/tabular-migrations.d.ts +19 -0
- package/dist/tabular-migrations.js +257 -0
- package/dist/tabular-migrations.js.map +18 -0
- package/dist/tabular-storage/assertions/countMatchesQuery.d.ts +7 -0
- package/dist/tabular-storage/assertions/guardParity.d.ts +25 -0
- package/dist/tabular-storage/assertions/inListCriterion.d.ts +14 -0
- package/dist/tabular-storage/assertions/joinBoundedLeftRead.d.ts +29 -0
- package/dist/tabular-storage/assertions/joinPushdown.d.ts +13 -0
- package/dist/tabular-storage/assertions/notInListCriterion.d.ts +15 -0
- package/dist/tabular-storage/assertions/strategyParity.d.ts +31 -0
- package/dist/tabular-storage/assertions/subscribeToChanges.d.ts +21 -0
- package/dist/tabular-storage/assertions/vectorColumnFormat.d.ts +7 -0
- package/dist/tabular-storage/assertions/withConnectionTransaction.d.ts +7 -0
- package/dist/tabular-storage/assertions/withTransactionRollback.d.ts +7 -0
- package/dist/tabular-storage/fixtures.d.ts +33 -0
- package/dist/tabular-storage/joinFixtures.d.ts +66 -0
- package/dist/tabular-storage/runTabularJoinContract.d.ts +24 -0
- package/dist/tabular-storage/runTabularStorageContract.d.ts +9 -0
- package/dist/tabular-storage/types.d.ts +102 -0
- package/dist/tabular-storage.d.ts +25 -0
- package/dist/tabular-storage.js +1477 -0
- package/dist/tabular-storage.js.map +27 -0
- package/dist/vector-storage/assertions/dimensionValidation.d.ts +17 -0
- package/dist/vector-storage/assertions/legacyEncoding.d.ts +16 -0
- package/dist/vector-storage/assertions/metadataFilter.d.ts +16 -0
- package/dist/vector-storage/assertions/searchOptions.d.ts +16 -0
- package/dist/vector-storage/assertions/shared.d.ts +23 -0
- package/dist/vector-storage/assertions/similarityRanking.d.ts +18 -0
- package/dist/vector-storage/assertions/similaritySearchEvent.d.ts +16 -0
- package/dist/vector-storage/assertions/vectorRoundTrip.d.ts +17 -0
- package/dist/vector-storage/runVectorStorageContract.d.ts +20 -0
- package/dist/vector-storage/types.d.ts +87 -0
- package/dist/vector-storage.d.ts +19 -0
- package/dist/vector-storage.js +412 -0
- package/dist/vector-storage.js.map +21 -0
- package/dist/web-search/assertions/capabilityAgreement.d.ts +23 -0
- package/dist/web-search/assertions/credentialNaming.d.ts +21 -0
- package/dist/web-search/assertions/dateFiltering.d.ts +22 -0
- package/dist/web-search/assertions/domainNormalization.d.ts +24 -0
- package/dist/web-search/assertions/maxResultsCeiling.d.ts +22 -0
- package/dist/web-search/assertions/reportedQuery.d.ts +18 -0
- package/dist/web-search/assertions/signalThreading.d.ts +20 -0
- package/dist/web-search/fixtures.d.ts +40 -0
- package/dist/web-search/runWebSearchProviderConformance.d.ts +24 -0
- package/dist/web-search/types.d.ts +79 -0
- package/dist/web-search.d.ts +19 -0
- package/dist/web-search.js +560 -0
- package/dist/web-search.js.map +18 -0
- package/dist/worker-proxy/assertions/backlogOrdering.d.ts +7 -0
- package/dist/worker-proxy/assertions/disposeTerminatesWorker.d.ts +8 -0
- package/dist/worker-proxy/assertions/errorPropagation.d.ts +7 -0
- package/dist/worker-proxy/assertions/providerCallHelpers.d.ts +23 -0
- package/dist/worker-proxy/browserOnlyStub.d.ts +7 -0
- package/dist/worker-proxy/runWorkerProxyBoundary.d.ts +7 -0
- package/dist/worker-proxy/types.d.ts +47 -0
- package/dist/worker-proxy.d.ts +16 -0
- package/dist/worker-proxy.js +389 -0
- package/dist/worker-proxy.js.map +17 -0
- package/package.json +120 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Steven Roussey <sroussey@gmail.com>
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import type { WebSearchProviderConformanceOpts } from "../types";
|
|
7
|
+
/**
|
|
8
|
+
* A credential is named for a provider, never for the request.
|
|
9
|
+
*
|
|
10
|
+
* `credential_keys` maps provider name to credential-store key, and the key
|
|
11
|
+
* sent is the one named for the provider that RAN — so a key issued for one
|
|
12
|
+
* vendor cannot leave with a request to another. That is not a tidiness rule:
|
|
13
|
+
* a search API key is a bearer credential against a metered account, and one
|
|
14
|
+
* forwarded to the wrong vendor has been disclosed to them.
|
|
15
|
+
*
|
|
16
|
+
* The bare `credential_key` port survives for a pinned provider, where the
|
|
17
|
+
* vendor is unambiguous, and is refused under `"auto"` for the same reason:
|
|
18
|
+
* routing picks the vendor at run time, so an unnamed key goes wherever it
|
|
19
|
+
* lands.
|
|
20
|
+
*/
|
|
21
|
+
export declare function credentialNamingBlock(opts: WebSearchProviderConformanceOpts): void;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Steven Roussey <sroussey@gmail.com>
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import type { WebSearchProviderConformanceOpts } from "../types";
|
|
7
|
+
/**
|
|
8
|
+
* Date filtering is never emulated, and never half-sent.
|
|
9
|
+
*
|
|
10
|
+
* Post-filtering by `publishedDate` breaks `maxResults` and drops every result
|
|
11
|
+
* whose date the provider omitted, so a provider that cannot filter server-side
|
|
12
|
+
* declares `dateFilter: false` and such a request is refused rather than
|
|
13
|
+
* approximated. That is the half most people expect.
|
|
14
|
+
*
|
|
15
|
+
* The other half is what a provider declaring `true` then owes: something for
|
|
16
|
+
* EVERY range this task accepts. The real APIs take closed intervals — Brave's
|
|
17
|
+
* `freshness`, Gemini's `timeRangeFilter` — so a half-open range has to be
|
|
18
|
+
* filled at the open end. Dropping it instead reports a bound as honored on a
|
|
19
|
+
* search that ran unfiltered, which is the failure the refusal above exists to
|
|
20
|
+
* avoid, arrived at by a different road.
|
|
21
|
+
*/
|
|
22
|
+
export declare function dateFilteringBlock(opts: WebSearchProviderConformanceOpts): void;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Steven Roussey <sroussey@gmail.com>
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import type { WebSearchProviderConformanceOpts } from "../types";
|
|
7
|
+
/**
|
|
8
|
+
* A domain entry means the same thing whichever route carries it.
|
|
9
|
+
*
|
|
10
|
+
* There are two routes — a native domain list, and a `site:` clause spliced
|
|
11
|
+
* into the query — and the same entry has to reduce identically on both. It did
|
|
12
|
+
* not once: reducing only on the way into an operator left `"https://arxiv.org/"`
|
|
13
|
+
* restricting one route and reaching a vendor API scheme-first on the other,
|
|
14
|
+
* where it matches nothing. Under `"auto"` the caller cannot even tell which
|
|
15
|
+
* route ran, so the search comes back successful, reports the provider, and
|
|
16
|
+
* silently searched the whole web.
|
|
17
|
+
*
|
|
18
|
+
* An unusable entry is refused for the same reason, and for EVERY provider
|
|
19
|
+
* rather than only the ones translating to `site:`. Dropping it is the same
|
|
20
|
+
* trade one step worse: the translation emits no clause for an empty list and
|
|
21
|
+
* the task clears the list afterwards, so one bad entry in a one-entry list
|
|
22
|
+
* removes the restriction from both paths at once.
|
|
23
|
+
*/
|
|
24
|
+
export declare function domainNormalizationBlock(opts: WebSearchProviderConformanceOpts): void;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Steven Roussey <sroussey@gmail.com>
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import type { WebSearchProviderConformanceOpts } from "../types";
|
|
7
|
+
/**
|
|
8
|
+
* `maxResults` is a ceiling, and it means the same thing on every route.
|
|
9
|
+
*
|
|
10
|
+
* It is the one option `unhonorableOptions` deliberately never reports: asking
|
|
11
|
+
* for more results than a provider's cap is not a requirement the way a domain
|
|
12
|
+
* restriction is, so an over-large value is clamped rather than refused. That
|
|
13
|
+
* asymmetry is what makes the rest of it worth asserting — a provider is free
|
|
14
|
+
* to return fewer, and never free to return more, and neither half is visible
|
|
15
|
+
* to a caller who gets a plausible-looking list back.
|
|
16
|
+
*
|
|
17
|
+
* Three providers needed a fix for exactly this in one window, each found by
|
|
18
|
+
* hand on its own route: an API with no result parameter has to apply the bound
|
|
19
|
+
* itself after the fact, and every one that forgot returned whatever the vendor
|
|
20
|
+
* felt like.
|
|
21
|
+
*/
|
|
22
|
+
export declare function maxResultsCeilingBlock(opts: WebSearchProviderConformanceOpts): void;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Steven Roussey <sroussey@gmail.com>
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import type { WebSearchProviderConformanceOpts } from "../types";
|
|
7
|
+
/**
|
|
8
|
+
* The `query` output port is the query that ran, and `provider` is who ran it.
|
|
9
|
+
*
|
|
10
|
+
* Both exist because the task rewrites one and chooses the other. A
|
|
11
|
+
* `query-operator` provider has its domain restriction spliced into the query
|
|
12
|
+
* as `site:` clauses, so the string that ran is not the string the caller
|
|
13
|
+
* passed — and an adapter that echoes the input instead is reporting a search
|
|
14
|
+
* that did not happen. Under `"auto"` the caller did not pick the vendor
|
|
15
|
+
* either, so `provider` is the only record of which quota was spent and which
|
|
16
|
+
* engine's ranking produced the list.
|
|
17
|
+
*/
|
|
18
|
+
export declare function reportedQueryBlock(opts: WebSearchProviderConformanceOpts): void;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Steven Roussey <sroussey@gmail.com>
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import type { WebSearchProviderConformanceOpts } from "../types";
|
|
7
|
+
/**
|
|
8
|
+
* The run's signal reaches the call, not just a check in front of it.
|
|
9
|
+
*
|
|
10
|
+
* A `throwIfAborted()` before the request answers the easy half — a run already
|
|
11
|
+
* cancelled does not start one — and leaves the expensive half alone: a
|
|
12
|
+
* cancelled run whose request is already in flight keeps it in flight, and a
|
|
13
|
+
* grounded turn nobody is waiting for is still billed for every token it
|
|
14
|
+
* generates. The signal has to be handed to the transport itself.
|
|
15
|
+
*
|
|
16
|
+
* Asserted on what the adapter passed outward rather than on how fast the
|
|
17
|
+
* promise rejects. A rejection proves the task stopped waiting; only the signal
|
|
18
|
+
* on the call proves the call stopped running.
|
|
19
|
+
*/
|
|
20
|
+
export declare function signalThreadingBlock(opts: WebSearchProviderConformanceOpts): void;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Steven Roussey <sroussey@gmail.com>
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import type { IWebSearchProvider, WebSearchRequest } from "@workglow/web-search";
|
|
7
|
+
import type { WebSearchConformanceHarness } from "./types";
|
|
8
|
+
/** The query every assertion searches for, so a rewrite of it is visible. */
|
|
9
|
+
export declare const PROBE_QUERY = "transformer architecture";
|
|
10
|
+
/**
|
|
11
|
+
* A provider registered beside the one under test, so `"auto"` has somewhere
|
|
12
|
+
* else to land and a credential can be named for a vendor that is not this one.
|
|
13
|
+
*
|
|
14
|
+
* It declares everything, which is what makes it a useful foil: routing prefers
|
|
15
|
+
* a provider a key is named for, so a decoy that could serve the request is the
|
|
16
|
+
* only way to catch a key reaching the wrong vendor.
|
|
17
|
+
*/
|
|
18
|
+
export declare function decoyProvider(name: string, onSearch?: (r: WebSearchRequest) => void): IWebSearchProvider;
|
|
19
|
+
/** Registers the provider under test alone, the state most assertions want. */
|
|
20
|
+
export declare function registerOnly(harness: WebSearchConformanceHarness): void;
|
|
21
|
+
/**
|
|
22
|
+
* Whether a domain restriction reaches this provider as a list or as `site:`
|
|
23
|
+
* operators spliced into the query. The exclude direction defaults to the
|
|
24
|
+
* include direction, exactly as the task reads it.
|
|
25
|
+
*/
|
|
26
|
+
export declare function domainRoutes(provider: IWebSearchProvider): {
|
|
27
|
+
readonly include: "native" | "query-operator" | false;
|
|
28
|
+
readonly exclude: "native" | "query-operator" | false;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Everything one search sent, as a single string to look for a substring in.
|
|
32
|
+
*
|
|
33
|
+
* The decoded rendering is appended rather than substituted, so a value matches
|
|
34
|
+
* whichever way this provider carried it. One sends `site:example.com` in a
|
|
35
|
+
* JSON body and the next sends `site%3Aexample.com` in a query string; both are
|
|
36
|
+
* the same restriction, and a suite that reads only the raw bytes would report
|
|
37
|
+
* the second as a restriction that never left. Appending keeps the equality
|
|
38
|
+
* assertions exact — two sends are equal here only if their raw forms were.
|
|
39
|
+
*/
|
|
40
|
+
export declare function sentText(harness: WebSearchConformanceHarness): string;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Steven Roussey <sroussey@gmail.com>
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import type { WebSearchProviderConformanceOpts } from "./types";
|
|
7
|
+
/**
|
|
8
|
+
* Everything an `IWebSearchProvider` owes a caller, over every adapter.
|
|
9
|
+
*
|
|
10
|
+
* Seven ship — three that own an HTTP fetch, four that reach a vendor SDK for
|
|
11
|
+
* model-grounded search — and they had no shared assertions at all. What that
|
|
12
|
+
* cost is legible in the package's own history: three of its twelve fixes in
|
|
13
|
+
* one window were "make property P true on every route", each found by hand,
|
|
14
|
+
* one route at a time, after the route that already had it was shipped. A
|
|
15
|
+
* property that has to hold on every route is what a parameterized suite is
|
|
16
|
+
* for.
|
|
17
|
+
*
|
|
18
|
+
* So the assertions are derived from `WebSearchCapabilities` rather than
|
|
19
|
+
* written per provider. The record already declares what each adapter can do;
|
|
20
|
+
* reading it and asserting the matching behaviour is what makes the suite still
|
|
21
|
+
* true after a provider changes what it supports — which is the event worth
|
|
22
|
+
* catching, and the one a hand-written expectation silently outlives.
|
|
23
|
+
*/
|
|
24
|
+
export declare function runWebSearchProviderConformance(opts: WebSearchProviderConformanceOpts): void;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Steven Roussey <sroussey@gmail.com>
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import type { IWebSearchProvider, WebSearchRequest, WebSearchResponse } from "@workglow/web-search";
|
|
7
|
+
/**
|
|
8
|
+
* One provider, wired to a transport that answers it instead of a vendor.
|
|
9
|
+
*
|
|
10
|
+
* The transport is the adapter's own boundary, not a network stub: an HTTP
|
|
11
|
+
* provider is intercepted at the `FetchUrlTask` it owns, an SDK-backed one at
|
|
12
|
+
* the client handed to its constructor. Both report what went out as text,
|
|
13
|
+
* which is what lets one suite assert "the option reached the wire" across two
|
|
14
|
+
* transports that share no shape.
|
|
15
|
+
*/
|
|
16
|
+
export interface WebSearchConformanceHarness {
|
|
17
|
+
readonly provider: IWebSearchProvider;
|
|
18
|
+
/**
|
|
19
|
+
* Everything the adapter sent outward, one entry per call, flattened to text
|
|
20
|
+
* — a URL with its query string and body, or the JSON of the SDK arguments.
|
|
21
|
+
*
|
|
22
|
+
* Flattened rather than structured because every assertion over it is
|
|
23
|
+
* differential: the same search with and without one option has to send
|
|
24
|
+
* something different. That question has an answer for every provider, where
|
|
25
|
+
* "which field carries the date bound" has a different one for each.
|
|
26
|
+
*/
|
|
27
|
+
readonly sent: () => readonly string[];
|
|
28
|
+
/** The abort signal each outbound call was given, in call order. */
|
|
29
|
+
readonly signals: () => ReadonlyArray<AbortSignal | undefined>;
|
|
30
|
+
/** Runs the provider directly against this transport, under `signal`. */
|
|
31
|
+
readonly search: (request: WebSearchRequest, signal: AbortSignal) => Promise<WebSearchResponse>;
|
|
32
|
+
/** Removes the interception. Called after every test. */
|
|
33
|
+
readonly dispose: () => void;
|
|
34
|
+
}
|
|
35
|
+
export interface WebSearchHarnessRequest {
|
|
36
|
+
/** How many results the canned answer carries. */
|
|
37
|
+
readonly resultCount: number;
|
|
38
|
+
}
|
|
39
|
+
export interface WebSearchProviderConformanceOpts {
|
|
40
|
+
readonly name: string;
|
|
41
|
+
readonly skip?: boolean;
|
|
42
|
+
readonly timeout?: number;
|
|
43
|
+
/**
|
|
44
|
+
* A fresh provider and transport per test. Nothing is shared between tests: a
|
|
45
|
+
* registry is process-wide, and a provider that cached a client in one test
|
|
46
|
+
* would answer the next one's assertions about what it sent.
|
|
47
|
+
*/
|
|
48
|
+
readonly createHarness: (request: WebSearchHarnessRequest) => Promise<WebSearchConformanceHarness>;
|
|
49
|
+
/**
|
|
50
|
+
* Whether the run's `AbortSignal` is handed to the outbound call itself.
|
|
51
|
+
*
|
|
52
|
+
* True for an adapter reaching a vendor SDK, where nothing else would stop a
|
|
53
|
+
* grounded turn that is still being billed. False for one whose fetch this
|
|
54
|
+
* package owns: cancellation reaches it through the `FetchUrlTask` it owns
|
|
55
|
+
* and that task's own scope, so the adapter passes no signal of its own and
|
|
56
|
+
* there is nothing at this boundary to observe. A flag rather than a silent
|
|
57
|
+
* skip, because "this adapter drops the signal" and "this adapter never held
|
|
58
|
+
* one" are the two answers that must not look alike.
|
|
59
|
+
*/
|
|
60
|
+
readonly signalReachesTransport: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Whether this provider's date parameter is a CLOSED interval, so a half-open
|
|
63
|
+
* range has its missing end filled rather than sent half.
|
|
64
|
+
*
|
|
65
|
+
* Brave's `freshness` and Gemini's `timeRangeFilter` are closed and fill;
|
|
66
|
+
* Tavily sets `start_date` and `end_date` independently and correctly sends
|
|
67
|
+
* only the bound it was given. A flag rather than something the suite infers,
|
|
68
|
+
* because the two are indistinguishable from outside — and the failure it
|
|
69
|
+
* guards is a provider that drops the open end and reports the bound as
|
|
70
|
+
* honored on a search that ran unfiltered at that end. Meaningless, and
|
|
71
|
+
* ignored, when `dateFilter` is false.
|
|
72
|
+
*/
|
|
73
|
+
readonly fillsOpenDateBounds: boolean;
|
|
74
|
+
/**
|
|
75
|
+
* A domain this provider's canned results sit on, used where a request has to
|
|
76
|
+
* name a real-looking host. Defaults to `example.com`.
|
|
77
|
+
*/
|
|
78
|
+
readonly sampleDomain?: string;
|
|
79
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Steven Roussey <sroussey@gmail.com>
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Conformance suite for `IWebSearchProvider` — what a provider declares in its
|
|
8
|
+
* capability record is what it refuses, what it attempts, and what it sends.
|
|
9
|
+
*/
|
|
10
|
+
export * from "./web-search/assertions/capabilityAgreement";
|
|
11
|
+
export * from "./web-search/assertions/credentialNaming";
|
|
12
|
+
export * from "./web-search/assertions/dateFiltering";
|
|
13
|
+
export * from "./web-search/assertions/domainNormalization";
|
|
14
|
+
export * from "./web-search/assertions/maxResultsCeiling";
|
|
15
|
+
export * from "./web-search/assertions/reportedQuery";
|
|
16
|
+
export * from "./web-search/assertions/signalThreading";
|
|
17
|
+
export * from "./web-search/fixtures";
|
|
18
|
+
export * from "./web-search/runWebSearchProviderConformance";
|
|
19
|
+
export * from "./web-search/types";
|