@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,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/human-connector/MockHumanConnector.ts", "../src/human-connector/fixtures.ts", "../src/human-connector/runHumanConnectorConformance.ts", "../src/human-connector/assertions/abort.ts", "../src/creditExhaustedSkip.ts", "../src/itExpectFail.ts", "../src/human-connector/assertions/capabilityHonesty.ts", "../src/human-connector/assertions/concurrentIsolation.ts", "../src/human-connector/assertions/multiTurnFollowUp.ts", "../src/human-connector/assertions/notifyDisplayFastResolve.ts", "../src/human-connector/assertions/roundtrip.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * @license\n * Copyright 2026 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type {\n HumanResponseAction,\n IHumanConnector,\n IHumanRequest,\n IHumanResponse,\n} from \"@workglow/util\";\n\nimport type { MockResponseEntry, MockResponseScript } from \"./types\";\n\nexport interface MockHumanConnectorOpts {\n /** Default action when no script entry is queued. Defaults to \"accept\". */\n readonly defaultAction?: HumanResponseAction;\n /** Whether `followUp` is implemented. Defaults to true. */\n readonly supportsFollowUp?: boolean;\n}\n\ntype DeferredEntry = {\n readonly kind: \"deferred\";\n readonly resolved: { value: IHumanResponse | undefined };\n readonly settled: { done: boolean };\n readonly waiters: Array<(res: IHumanResponse) => void>;\n /** Reject hooks for awaiters when clear() runs while still pending. */\n readonly rejecters: Array<(err: Error) => void>;\n};\n\ntype ImmediateEntry = { readonly kind: \"immediate\"; readonly entry: MockResponseEntry };\n\ntype QueueEntry = ImmediateEntry | DeferredEntry;\n\nclass Script implements MockResponseScript {\n private readonly _received: IHumanRequest[] = [];\n private readonly _queue: QueueEntry[] = [];\n /**\n * All deferreds that have been pushed but not yet settled. Tracked\n * separately so clear() can reject pending awaiters even after the entry\n * has been shifted out of the queue by send().\n */\n private readonly _pendingDeferreds: DeferredEntry[] = [];\n\n get received(): ReadonlyArray<IHumanRequest> {\n return this._received;\n }\n\n recordReceived(req: IHumanRequest): void {\n this._received.push(req);\n }\n\n push(entry: MockResponseEntry): void {\n this._queue.push({ kind: \"immediate\", entry });\n }\n\n pushDeferred(): { release(response: IHumanResponse): void } {\n const def: DeferredEntry = {\n kind: \"deferred\",\n resolved: { value: undefined },\n settled: { done: false },\n waiters: [],\n rejecters: [],\n };\n this._queue.push(def);\n this._pendingDeferreds.push(def);\n return {\n release: (response) => {\n if (def.settled.done) return;\n def.settled.done = true;\n def.resolved.value = response;\n for (const w of def.waiters) w(response);\n def.waiters.length = 0;\n def.rejecters.length = 0;\n },\n };\n }\n\n shift(): QueueEntry | undefined {\n return this._queue.shift();\n }\n\n clear(): void {\n for (const def of this._pendingDeferreds) {\n if (def.settled.done) continue;\n def.settled.done = true;\n const err = new Error(\"MockHumanConnector script cleared while deferred was pending\");\n err.name = \"AbortError\";\n for (const r of def.rejecters) r(err);\n def.rejecters.length = 0;\n def.waiters.length = 0;\n }\n this._pendingDeferreds.length = 0;\n this._received.length = 0;\n this._queue.length = 0;\n }\n}\n\nfunction defaultAbortError(signal: AbortSignal): Error {\n if (signal.reason instanceof Error) return signal.reason;\n const err = new Error(\"The operation was aborted\");\n err.name = \"AbortError\";\n return err;\n}\n\nfunction awaitDeferred(def: DeferredEntry, signal: AbortSignal): Promise<IHumanResponse> {\n return new Promise<IHumanResponse>((resolve, reject) => {\n if (signal.aborted) {\n reject(defaultAbortError(signal));\n return;\n }\n if (def.settled.done && def.resolved.value !== undefined) {\n resolve(def.resolved.value);\n return;\n }\n const onAbort = (): void => {\n signal.removeEventListener(\"abort\", onAbort);\n reject(defaultAbortError(signal));\n };\n signal.addEventListener(\"abort\", onAbort, { once: true });\n def.waiters.push((res) => {\n signal.removeEventListener(\"abort\", onAbort);\n resolve(res);\n });\n def.rejecters.push((err) => {\n signal.removeEventListener(\"abort\", onAbort);\n reject(err);\n });\n });\n}\n\nexport class MockHumanConnector implements IHumanConnector {\n private readonly defaultAction: HumanResponseAction;\n private readonly _script: Script;\n readonly followUp?: (\n request: IHumanRequest,\n previous: IHumanResponse,\n signal: AbortSignal\n ) => Promise<IHumanResponse>;\n\n constructor(opts: MockHumanConnectorOpts = {}) {\n this.defaultAction = opts.defaultAction ?? \"accept\";\n this._script = new Script();\n if (opts.supportsFollowUp ?? true) {\n this.followUp = (request, _previous, signal) => this.send(request, signal);\n }\n }\n\n get script(): MockResponseScript {\n return this._script;\n }\n\n async send(request: IHumanRequest, signal: AbortSignal): Promise<IHumanResponse> {\n if (signal.aborted) throw defaultAbortError(signal);\n this._script.recordReceived(request);\n\n // notify/display fast-resolve without consuming the script queue.\n // The queue is for elicit responses; consuming an entry on a notify or\n // display request would corrupt deterministic scripting for any later\n // elicit prompt.\n if (request.kind === \"notify\" || request.kind === \"display\") {\n return {\n requestId: request.requestId,\n action: this.defaultAction,\n content: undefined,\n done: true,\n };\n }\n\n const entry = this._script.shift();\n if (entry === undefined) {\n return {\n requestId: request.requestId,\n action: this.defaultAction,\n content: undefined,\n done: true,\n };\n }\n if (entry.kind === \"deferred\") {\n const res = await awaitDeferred(entry, signal);\n return this.shape(request, res);\n }\n const resolved = typeof entry.entry === \"function\" ? await entry.entry(request) : entry.entry;\n return this.shape(request, resolved);\n }\n\n /**\n * Content belongs to an accepted elicit and nothing else, whatever a script\n * put beside the action — the reference connector holds the same contract\n * every adapter is measured against. A confirm answers with the decision\n * alone, and a refused elicit answers with no data, since `HumanInputTask`\n * spreads `content` onto its output ports either way.\n */\n private shape(request: IHumanRequest, response: IHumanResponse): IHumanResponse {\n const carriesContent = request.kind === \"elicit\" && response.action === \"accept\";\n return {\n ...response,\n requestId: request.requestId,\n content: carriesContent ? response.content : undefined,\n };\n }\n}\n",
|
|
6
|
+
"/**\n * @license\n * Copyright 2026 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { DataPortSchema } from \"@workglow/util/schema\";\n\nimport type { ConformanceFixture } from \"./types\";\n\nconst elicitSchema: DataPortSchema = {\n type: \"object\",\n properties: {\n approved: {\n type: \"boolean\",\n title: \"Approved\",\n description: \"Whether the request is approved\",\n },\n reason: { type: \"string\", title: \"Reason\" },\n },\n required: [\"approved\"],\n additionalProperties: false,\n};\n\nconst emptySchema: DataPortSchema = {\n type: \"object\",\n properties: {},\n additionalProperties: true,\n};\n\nexport const DEFAULT_HUMAN_CONFORMANCE_FIXTURE: ConformanceFixture = {\n elicitContentSchema: elicitSchema,\n elicitAcceptContent: { approved: true, reason: \"looks good\" },\n notifyRequest: {\n message: \"Job completed.\",\n contentSchema: emptySchema,\n contentData: { jobId: \"test-1\" },\n },\n displayRequest: {\n message: \"Here is the result.\",\n contentSchema: emptySchema,\n contentData: { result: 42 },\n },\n // A confirm's schema describes the action awaiting approval rather than\n // fields to fill in, so the data is what a person reads before deciding.\n confirmRequest: {\n message: 'Run workflow \"nightly-export\"?',\n contentSchema: {\n type: \"object\",\n properties: {\n action: { type: \"string\", title: \"Action\" },\n reaches: { type: \"string\", title: \"Reaches\" },\n },\n additionalProperties: true,\n },\n contentData: { action: \"Run workflow\", reaches: \"network:http → https://example.test\" },\n },\n abortGraceMs: 1000,\n};\n\nexport function resolveHumanConformanceFixture(\n override: Partial<ConformanceFixture> | undefined\n): ConformanceFixture {\n if (!override) return DEFAULT_HUMAN_CONFORMANCE_FIXTURE;\n return { ...DEFAULT_HUMAN_CONFORMANCE_FIXTURE, ...override };\n}\n",
|
|
7
|
+
"/**\n * @license\n * Copyright 2026 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { afterAll, beforeAll, beforeEach, describe } from \"vitest\";\n\nimport { abortBlock } from \"./assertions/abort\";\nimport { capabilityHonestyBlock } from \"./assertions/capabilityHonesty\";\nimport { concurrentIsolationBlock } from \"./assertions/concurrentIsolation\";\nimport { multiTurnFollowUpBlock } from \"./assertions/multiTurnFollowUp\";\nimport { notifyDisplayFastResolveBlock } from \"./assertions/notifyDisplayFastResolve\";\nimport { roundtripBlock } from \"./assertions/roundtrip\";\nimport { resolveHumanConformanceFixture } from \"./fixtures\";\nimport type { HumanConnectorConformanceHandle, HumanConnectorConformanceOpts } from \"./types\";\n\nexport { MockHumanConnector } from \"./MockHumanConnector\";\nexport type { HumanConnectorConformanceOpts } from \"./types\";\n\nexport function runHumanConnectorConformance(opts: HumanConnectorConformanceOpts): void {\n describe.skipIf(opts.skip)(`IHumanConnector conformance: ${opts.name}`, () => {\n let handle: HumanConnectorConformanceHandle | undefined;\n const getHandle = (): HumanConnectorConformanceHandle => {\n if (!handle) throw new Error(\"conformance handle not initialized\");\n return handle;\n };\n\n beforeAll(async () => {\n handle = await opts.factory();\n }, opts.timeout);\n\n beforeEach(() => {\n // Each assertion starts from a clean script + received list.\n handle?.script.clear();\n });\n\n afterAll(async () => {\n if (handle) await handle.dispose();\n });\n\n const fixture = resolveHumanConformanceFixture(opts.fixture);\n\n roundtripBlock(opts, fixture, getHandle);\n abortBlock(opts, fixture, getHandle);\n concurrentIsolationBlock(opts, fixture, getHandle);\n notifyDisplayFastResolveBlock(opts, fixture, getHandle);\n multiTurnFollowUpBlock(opts, fixture, getHandle);\n capabilityHonestyBlock(opts, fixture, getHandle);\n });\n}\n",
|
|
8
|
+
"/**\n * @license\n * Copyright 2026 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { IHumanRequest } from \"@workglow/util\";\nimport { describe, expect, it } from \"vitest\";\n\nimport { itExpectFail } from \"../../itExpectFail\";\nimport type {\n ConformanceFixture,\n HumanConnectorConformanceHandle,\n HumanConnectorConformanceOpts,\n} from \"../types\";\n\nfunction isAbortError(err: unknown): boolean {\n if (!err || typeof err !== \"object\") return false;\n const e = err as { name?: string; message?: string };\n return e.name === \"AbortError\" || /abort/i.test(e.message ?? \"\");\n}\n\nfunction elicitReq(fixture: ConformanceFixture, requestId: string): IHumanRequest {\n return {\n requestId,\n targetHumanId: \"default\",\n kind: \"elicit\",\n message: \"Please confirm.\",\n contentSchema: fixture.elicitContentSchema,\n contentData: undefined,\n expectsResponse: true,\n mode: \"single\",\n metadata: undefined,\n };\n}\n\nexport function abortBlock(\n opts: HumanConnectorConformanceOpts,\n fixture: ConformanceFixture,\n getHandle: () => HumanConnectorConformanceHandle\n): void {\n const expectFails = new Set(opts.expectedFailures ?? []);\n const itBefore = expectFails.has(\"abort.beforeSend\") ? itExpectFail : it;\n const itMid = expectFails.has(\"abort.midElicit\") ? itExpectFail : it;\n\n describe.skipIf(!opts.capabilities.elicit)(\"Abort\", () => {\n itBefore(\n \"send() with already-aborted signal rejects with AbortError\",\n async () => {\n const { connector } = getHandle();\n const ac = new AbortController();\n ac.abort();\n let caught: unknown;\n try {\n await connector.send(elicitReq(fixture, \"ab-pre-1\"), ac.signal);\n } catch (err) {\n caught = err;\n }\n expect(caught).toBeDefined();\n expect(isAbortError(caught)).toBe(true);\n },\n opts.timeout\n );\n\n if (opts.capabilities.abortMidElicit) {\n itMid(\n \"send() rejects with AbortError when signal aborts mid-elicit\",\n async () => {\n const { connector, script } = getHandle();\n const handle = script.pushDeferred();\n const ac = new AbortController();\n const start = Date.now();\n const promise = connector.send(elicitReq(fixture, \"ab-mid-1\"), ac.signal);\n setTimeout(() => ac.abort(), 25);\n\n let caught: unknown;\n try {\n await promise;\n } catch (err) {\n caught = err;\n }\n const elapsed = Date.now() - start;\n expect(caught).toBeDefined();\n expect(isAbortError(caught)).toBe(true);\n expect(elapsed).toBeLessThan(fixture.abortGraceMs * 4 + 2000);\n // Releasing after rejection must not throw or produce a stray response.\n expect(() =>\n handle.release({\n requestId: \"x\",\n action: \"accept\",\n content: undefined,\n done: true,\n })\n ).not.toThrow();\n },\n opts.timeout\n );\n }\n });\n}\n",
|
|
9
|
+
"/**\n * @license\n * Copyright 2026 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { it as vitestIt } from \"vitest\";\n\n/**\n * Live provider tests treat \"the account is out of credits\" as a billing\n * condition rather than a product bug. A key being present is not the same as\n * the account being able to pay; HuggingFace / OpenRouter 402s, Anthropic's\n * credit-balance error, DeepSeek's `Insufficient Balance`, and OpenAI's\n * `insufficient_quota` are all billing.\n *\n * Rate limits (429 + `rate_limit_exceeded`) are NOT this: those are transient\n * and the existing retry policy handles them.\n *\n * Where it skips and where it fails is a deliberate split\n * ({@link creditExhaustionSkipsTest}): on CI it skips for every provider,\n * because a red build tells a reviewer nothing they can act on; locally it\n * fails, because the developer running the suite is the one who can top the\n * account up. `WORKGLOW_CREDIT_EXHAUSTED_SKIP=1` forces the skip locally\n * (and `=0` forces the failure on CI).\n */\n\nconst BILLING_CODES = new Set([\n \"insufficient_quota\",\n \"billing_hard_limit_reached\",\n \"insufficient_credits\",\n \"insufficient_balance\",\n]);\n\nconst CREDIT_MESSAGE =\n /insufficient credits|not enough credits|no credits remaining|out of credits|insufficient[\\s_-]balance|balance is (?:too low|insufficient)|exceeded[\\s\\S]{0,80}credits|monthly included credits|payment required|exceeded your current quota|check your plan and billing|billing_hard_limit/i;\n\n/**\n * A 402 that survives only as text. `classifyProviderError` rebuilds the error\n * as a `PermanentJobError` carrying the message and no `cause`, so by the time\n * a live test sees a DeepSeek billing failure the numeric `status` is gone and\n * the OpenAI-SDK message shape (`402 Insufficient Balance`) is all that is\n * left. Anchored to an HTTP-shaped position — start of a line, or after a\n * summary line's colon — so prose that merely contains the number\n * (\"Item 402(c)\") is not scavenged as a status.\n */\nconst HTTP_402_MESSAGE =\n /\\bHTTP\\/?\\d?(?:\\.\\d)?\\s*402\\b|\\bstatus(?:\\s*code)?\\s*[:=]\\s*402\\b|\\berror code:?\\s*402\\b|(?:^|:\\s+)402\\s+[a-z]/im;\n\nexport function isCreditExhaustedError(err: unknown): boolean {\n const seen = new Set<unknown>();\n let current: unknown = err;\n while (current !== undefined && current !== null && !seen.has(current)) {\n seen.add(current);\n if (matchesCreditExhaustion(current)) return true;\n current = causeOf(current);\n }\n return false;\n}\n\nfunction matchesCreditExhaustion(err: unknown): boolean {\n if (typeof err === \"string\") return messageLooksLikeCreditExhaustion(err);\n\n if (!err || typeof err !== \"object\") return false;\n\n const rec = err as Record<string, unknown>;\n const status = numericStatus(rec);\n if (status === 402) return true;\n\n const code = stringish(rec.code) ?? stringish(nested(rec.error, \"code\"));\n const type = stringish(rec.type) ?? stringish(nested(rec.error, \"type\"));\n if (code && BILLING_CODES.has(code)) return true;\n if (type && BILLING_CODES.has(type)) return true;\n\n const message = [\n stringish(rec.message),\n stringish(nested(rec.error, \"message\")),\n stringifyUnknown(err),\n ]\n .filter((part): part is string => typeof part === \"string\")\n .join(\"\\n\");\n return messageLooksLikeCreditExhaustion(message);\n}\n\nfunction messageLooksLikeCreditExhaustion(message: string): boolean {\n return CREDIT_MESSAGE.test(message) || HTTP_402_MESSAGE.test(message);\n}\n\nfunction numericStatus(rec: Record<string, unknown>): number | undefined {\n for (const key of [\"status\", \"statusCode\"] as const) {\n const value = rec[key];\n if (typeof value === \"number\" && Number.isFinite(value)) return value;\n }\n const nestedStatus = nested(rec.error, \"status\");\n if (typeof nestedStatus === \"number\" && Number.isFinite(nestedStatus)) return nestedStatus;\n return undefined;\n}\n\nfunction nested(value: unknown, key: string): unknown {\n if (!value || typeof value !== \"object\") return undefined;\n return (value as Record<string, unknown>)[key];\n}\n\nfunction stringish(value: unknown): string | undefined {\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\nfunction stringifyUnknown(err: unknown): string {\n if (err instanceof Error) return err.message;\n return String(err);\n}\n\nfunction causeOf(err: unknown): unknown {\n if (!err || typeof err !== \"object\") return undefined;\n return (err as { cause?: unknown }).cause;\n}\n\nexport function creditSkipNote(err: unknown): string {\n const message = err instanceof Error ? err.message : String(err);\n return `skipped: provider out of credits (${message})`;\n}\n\nconst SKIP_OVERRIDE_ENV = \"WORKGLOW_CREDIT_EXHAUSTED_SKIP\";\n\nfunction isTruthyFlag(value: string): boolean {\n return !/^(?:0|false|off|no)$/i.test(value.trim());\n}\n\n/**\n * Whether a billing failure skips the test or fails it.\n *\n * CI skips: nobody watching a build can top an account up, and one exhausted\n * key would otherwise turn every provider suite red for a change that touched\n * none of them — so the leniency is provider-agnostic by construction, keyed on\n * the environment rather than on which vendor ran out.\n *\n * Locally it fails, loudly, because the developer running the suite IS the\n * person who can act on it — and a silently green local run is how an\n * exhausted account goes unnoticed until CI is the only thing exercising the\n * provider at all.\n *\n * `WORKGLOW_CREDIT_EXHAUSTED_SKIP` overrides in both directions.\n */\nexport function creditExhaustionSkipsTest(\n env: Record<string, string | undefined> = process.env\n): boolean {\n const override = env[SKIP_OVERRIDE_ENV];\n if (override !== undefined && override.trim() !== \"\") return isTruthyFlag(override);\n for (const key of [\"CI\", \"GITHUB_ACTIONS\"] as const) {\n const value = env[key];\n if (value !== undefined && value.trim() !== \"\" && isTruthyFlag(value)) return true;\n }\n return false;\n}\n\n/**\n * The local-failure path. Rethrows the provider's own error unchanged — its\n * identity and stack are what makes the failure diagnosable — after one line\n * saying why it was not skipped and how to skip it.\n */\nfunction reportLocalCreditFailure(err: unknown): void {\n const message = err instanceof Error ? err.message : String(err);\n console.error(\n `provider out of credits (${message})\\n` +\n ` Failing because this is not CI. Top the account up, or set ${SKIP_OVERRIDE_ENV}=1 to skip instead.`\n );\n}\n\nexport async function runWithCreditSkip<T>(\n ctx: { skip: (note?: string) => void },\n fn: () => Promise<T> | T\n): Promise<T> {\n try {\n return await fn();\n } catch (err) {\n if (isCreditExhaustedError(err)) {\n if (!creditExhaustionSkipsTest()) {\n reportLocalCreditFailure(err);\n throw err;\n }\n ctx.skip(creditSkipNote(err));\n // vitest's skip() is `never` (throws). Mocks used in unit tests return.\n return undefined as T;\n }\n throw err;\n }\n}\n\n/**\n * Wraps one test body. This is the seam every conformance assertion actually\n * runs through (the exported {@link it} proxies each callback through it), so\n * the CI-skips / locally-fails policy is applied here rather than at each\n * call site.\n */\nexport function wrapTestBodyForCreditSkip(\n fn: (...args: never[]) => unknown\n): (...args: never[]) => unknown {\n return async function wrapped(this: unknown, ...args: never[]): Promise<unknown> {\n try {\n return await fn.apply(this, args);\n } catch (err) {\n if (!isCreditExhaustedError(err)) throw err;\n if (!creditExhaustionSkipsTest()) {\n reportLocalCreditFailure(err);\n throw err;\n }\n const ctx = args[0] as { skip?: (note?: string) => void } | undefined;\n if (ctx && typeof ctx.skip === \"function\") {\n ctx.skip(creditSkipNote(err));\n return undefined;\n }\n // bun:test has no TestContext.skip; passing is \"don't fail the test\".\n console.warn(creditSkipNote(err));\n return undefined;\n }\n };\n}\n\nfunction wrapCallable(fn: CallableFunction): CallableFunction {\n return new Proxy(fn, {\n apply(target, thisArg, argArray: unknown[]) {\n const wrapped = argArray.map((arg) =>\n typeof arg === \"function\" ? wrapTestBodyForCreditSkip(arg as never) : arg\n );\n const result = Reflect.apply(target, thisArg, wrapped);\n return typeof result === \"function\" ? wrapCallable(result) : result;\n },\n get(target, prop, receiver) {\n const value = Reflect.get(target, prop, receiver);\n if (typeof value === \"function\") {\n return wrapCallable((value as CallableFunction).bind(target));\n }\n return value;\n },\n });\n}\n\n/**\n * Drop-in `it` for live provider tests: a credit/billing error skips the test on\n * CI and fails it locally (see {@link creditExhaustionSkipsTest}).\n */\nexport const it = wrapCallable(vitestIt) as typeof vitestIt;\n",
|
|
10
|
+
"/**\n * @license\n * Copyright 2026 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { isCreditExhaustedError, it } from \"./creditExhaustedSkip\";\n\ntype ItFn = (name: string, fn: () => Promise<void> | void, timeout?: number) => void;\n\n/**\n * Cross-runner polyfill for `it.fails`. Vitest exposes it natively; bun's\n * native `bun test` runner does not. Under bun we wrap the body in a\n * try/catch and assert that it threw — so a passing-when-expected-fail\n * test still surfaces as a CI failure (signalling \"remove from\n * expectedFailures\").\n */\nexport const itExpectFail: ItFn = (name, fn, timeout) => {\n const native = (it as unknown as { fails?: ItFn }).fails;\n if (typeof native === \"function\") {\n native(name, fn, timeout);\n return;\n }\n it(\n `${name} [expected-fail]`,\n async () => {\n let passed = false;\n try {\n await fn();\n passed = true;\n } catch (err) {\n // Billing exhaustion is not the adapter bug this marker records.\n if (isCreditExhaustedError(err)) throw err;\n // expected; the test was supposed to fail\n }\n if (passed) {\n throw new Error(\n `Test \"${name}\" was marked as expected-fail but passed. Remove its name from opts.expectedFailures.`\n );\n }\n },\n timeout\n );\n};\n",
|
|
11
|
+
"/**\n * @license\n * Copyright 2026 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { HumanInteractionKind, IHumanRequest } from \"@workglow/util\";\nimport { describe, expect, it } from \"vitest\";\n\nimport { itExpectFail } from \"../../itExpectFail\";\nimport type {\n ConformanceFixture,\n HumanConnectorConformanceHandle,\n HumanConnectorConformanceOpts,\n} from \"../types\";\n\nfunction buildReq(\n fixture: ConformanceFixture,\n kind: HumanInteractionKind,\n requestId: string\n): IHumanRequest {\n const base =\n kind === \"elicit\"\n ? { message: \"elicit\", contentSchema: fixture.elicitContentSchema, contentData: undefined }\n : kind === \"notify\"\n ? fixture.notifyRequest\n : kind === \"confirm\"\n ? fixture.confirmRequest\n : fixture.displayRequest;\n return {\n requestId,\n targetHumanId: \"default\",\n kind,\n message: base.message,\n contentSchema: base.contentSchema,\n contentData: base.contentData,\n expectsResponse: kind === \"elicit\" || kind === \"confirm\",\n mode: \"single\",\n metadata: undefined,\n };\n}\n\nexport function capabilityHonestyBlock(\n opts: HumanConnectorConformanceOpts,\n fixture: ConformanceFixture,\n getHandle: () => HumanConnectorConformanceHandle\n): void {\n const expectFails = new Set(opts.expectedFailures ?? []);\n const itFn = expectFails.has(\"capabilityHonesty\") ? itExpectFail : it;\n\n describe(\"Capability honesty\", () => {\n itFn(\n \"multiTurn:false implies followUp is undefined (not silent no-op)\",\n () => {\n if (opts.capabilities.multiTurn) return;\n const { connector } = getHandle();\n expect(connector.followUp).toBeUndefined();\n },\n opts.timeout\n );\n\n for (const kind of [\"notify\", \"display\", \"elicit\", \"confirm\"] as const) {\n itFn(\n `${kind}:false implies the connector either throws or surfaces a non-accept action (no silent accept)`,\n async () => {\n if (opts.capabilities[kind]) return;\n const { connector } = getHandle();\n const ac = new AbortController();\n let caught: unknown;\n let res: { action?: string } | undefined;\n try {\n res = await connector.send(buildReq(fixture, kind, `cap-${kind}`), ac.signal);\n } catch (err) {\n caught = err;\n }\n if (caught) {\n expect(caught).toBeDefined();\n return;\n }\n expect(res).toBeDefined();\n expect(res!.action).not.toBe(\"accept\");\n },\n opts.timeout\n );\n }\n });\n}\n",
|
|
12
|
+
"/**\n * @license\n * Copyright 2026 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { IHumanRequest, IHumanResponse } from \"@workglow/util\";\nimport { describe, expect, it } from \"vitest\";\n\nimport { itExpectFail } from \"../../itExpectFail\";\nimport type {\n ConformanceFixture,\n HumanConnectorConformanceHandle,\n HumanConnectorConformanceOpts,\n} from \"../types\";\n\nfunction elicitReq(fixture: ConformanceFixture, requestId: string): IHumanRequest {\n return {\n requestId,\n targetHumanId: \"default\",\n kind: \"elicit\",\n // The message embeds the requestId so adapters that don't expose\n // upstream requestIds across their transport (e.g. the MCP harness)\n // can still discriminate concurrent requests.\n message: `Please confirm ${requestId}.`,\n contentSchema: fixture.elicitContentSchema,\n contentData: undefined,\n expectsResponse: true,\n mode: \"single\",\n metadata: undefined,\n };\n}\n\nexport function concurrentIsolationBlock(\n opts: HumanConnectorConformanceOpts,\n fixture: ConformanceFixture,\n getHandle: () => HumanConnectorConformanceHandle\n): void {\n const expectFails = new Set(opts.expectedFailures ?? []);\n const itConcurrent = expectFails.has(\"concurrent.isolation\") ? itExpectFail : it;\n\n describe.skipIf(!opts.capabilities.concurrent || !opts.capabilities.elicit)(\n \"Concurrent isolation\",\n () => {\n itConcurrent(\n \"two concurrent send() calls each receive their own scripted response\",\n async () => {\n const { connector, script } = getHandle();\n // Messages are of the form \"Please confirm <requestId>.\" — extract the id so\n // adapters that synthesise their own requestIds (e.g. the MCP harness) can\n // still echo the upstream id back through the content.reason field.\n const tagFromMessage = (message: string): string => {\n const m = message.match(/Please confirm (.+)\\./);\n return m ? m[1] : \"unknown\";\n };\n script.push((req): IHumanResponse => {\n const tag = tagFromMessage(req.message);\n return {\n requestId: req.requestId,\n action: \"accept\",\n content: { approved: true, reason: tag },\n done: true,\n };\n });\n script.push((req): IHumanResponse => {\n const tag = tagFromMessage(req.message);\n return {\n requestId: req.requestId,\n action: \"accept\",\n content: { approved: true, reason: tag },\n done: true,\n };\n });\n\n const ac = new AbortController();\n const p1 = connector.send(elicitReq(fixture, \"conc-A\"), ac.signal);\n const p2 = connector.send(elicitReq(fixture, \"conc-B\"), ac.signal);\n const [a, b] = await Promise.all([p1, p2]);\n\n expect(a.requestId).toBe(\"conc-A\");\n expect((a.content as { reason: string }).reason).toBe(\"conc-A\");\n expect(b.requestId).toBe(\"conc-B\");\n expect((b.content as { reason: string }).reason).toBe(\"conc-B\");\n },\n opts.timeout\n );\n }\n );\n}\n",
|
|
13
|
+
"/**\n * @license\n * Copyright 2026 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { IHumanRequest } from \"@workglow/util\";\nimport { describe, expect, it } from \"vitest\";\n\nimport { itExpectFail } from \"../../itExpectFail\";\nimport type {\n ConformanceFixture,\n HumanConnectorConformanceHandle,\n HumanConnectorConformanceOpts,\n} from \"../types\";\n\nfunction elicitReq(\n fixture: ConformanceFixture,\n requestId: string,\n mode: \"single\" | \"multi-turn\"\n): IHumanRequest {\n return {\n requestId,\n targetHumanId: \"default\",\n kind: \"elicit\",\n message: \"Multi-turn step.\",\n contentSchema: fixture.elicitContentSchema,\n contentData: undefined,\n expectsResponse: true,\n mode,\n metadata: undefined,\n };\n}\n\nfunction isAbortError(err: unknown): boolean {\n if (!err || typeof err !== \"object\") return false;\n const e = err as { name?: string; message?: string };\n return e.name === \"AbortError\" || /abort/i.test(e.message ?? \"\");\n}\n\nexport function multiTurnFollowUpBlock(\n opts: HumanConnectorConformanceOpts,\n fixture: ConformanceFixture,\n getHandle: () => HumanConnectorConformanceHandle\n): void {\n const expectFails = new Set(opts.expectedFailures ?? []);\n const itFn = expectFails.has(\"multiTurn.followUp\") ? itExpectFail : it;\n\n describe.skipIf(!opts.capabilities.multiTurn || !opts.capabilities.elicit)(\n \"Multi-turn followUp\",\n () => {\n itFn(\n \"followUp() called after done=false response returns terminal response\",\n async () => {\n const { connector, script } = getHandle();\n script.push({\n requestId: \"x\",\n action: \"accept\",\n content: { approved: false, reason: \"step1\" },\n done: false,\n });\n script.push({\n requestId: \"x\",\n action: \"accept\",\n content: { approved: true, reason: \"step2\" },\n done: true,\n });\n\n const ac = new AbortController();\n const req = elicitReq(fixture, \"mt-1\", \"multi-turn\");\n const first = await connector.send(req, ac.signal);\n expect(first.done).toBe(false);\n\n expect(typeof connector.followUp).toBe(\"function\");\n const second = await connector.followUp!(req, first, ac.signal);\n expect(second.done).toBe(true);\n expect(second.content).toEqual({ approved: true, reason: \"step2\" });\n },\n opts.timeout\n );\n\n if (opts.capabilities.abortMidElicit) {\n itFn(\n \"followUp() honors AbortSignal mid-flight\",\n async () => {\n const { connector, script } = getHandle();\n script.push({\n requestId: \"x\",\n action: \"accept\",\n content: { approved: false, reason: \"step1\" },\n done: false,\n });\n script.pushDeferred();\n const ac = new AbortController();\n const req = elicitReq(fixture, \"mt-2\", \"multi-turn\");\n const first = await connector.send(req, ac.signal);\n expect(first.done).toBe(false);\n const promise = connector.followUp!(req, first, ac.signal);\n setTimeout(() => ac.abort(), 25);\n let caught: unknown;\n try {\n await promise;\n } catch (err) {\n caught = err;\n }\n expect(caught).toBeDefined();\n expect(isAbortError(caught)).toBe(true);\n },\n opts.timeout\n );\n }\n }\n );\n}\n",
|
|
14
|
+
"/**\n * @license\n * Copyright 2026 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { HumanInteractionKind, IHumanRequest } from \"@workglow/util\";\nimport { describe, expect, it } from \"vitest\";\n\nimport { itExpectFail } from \"../../itExpectFail\";\nimport type {\n ConformanceFixture,\n HumanConnectorAssertionId,\n HumanConnectorConformanceHandle,\n HumanConnectorConformanceOpts,\n} from \"../types\";\n\nfunction buildReq(\n fixture: ConformanceFixture,\n kind: \"notify\" | \"display\",\n requestId: string\n): IHumanRequest {\n const base = kind === \"notify\" ? fixture.notifyRequest : fixture.displayRequest;\n return {\n requestId,\n targetHumanId: \"default\",\n kind: kind as HumanInteractionKind,\n message: base.message,\n contentSchema: base.contentSchema,\n contentData: base.contentData,\n expectsResponse: false,\n mode: \"single\",\n metadata: undefined,\n };\n}\n\nfunction elicitReq(fixture: ConformanceFixture, requestId: string): IHumanRequest {\n return {\n requestId,\n targetHumanId: \"default\",\n kind: \"elicit\",\n message: \"Follow-up after fast-resolve.\",\n contentSchema: fixture.elicitContentSchema,\n contentData: undefined,\n expectsResponse: true,\n mode: \"single\",\n metadata: undefined,\n };\n}\n\nfunction block(\n kind: \"notify\" | \"display\",\n enabled: boolean,\n failId: HumanConnectorAssertionId,\n opts: HumanConnectorConformanceOpts,\n fixture: ConformanceFixture,\n getHandle: () => HumanConnectorConformanceHandle\n): void {\n const expectFails = new Set(opts.expectedFailures ?? []);\n const itFn = expectFails.has(failId) ? itExpectFail : it;\n\n describe.skipIf(!enabled || !opts.capabilities.elicit)(`${kind} fast-resolve`, () => {\n itFn(\n `${kind} resolves with action=accept, done=true, and does not consume scripted elicit responses`,\n async () => {\n const { connector, script } = getHandle();\n // Preload an elicit response; if the connector incorrectly consumes\n // the queue on a notify/display request, the next elicit would fall\n // through to the connector's default and the assertion below would\n // surface the regression.\n const sentinel = { approved: true, reason: \"sentinel\" };\n script.push({ requestId: \"ignored\", action: \"accept\", content: sentinel, done: true });\n\n const ac = new AbortController();\n const res = await connector.send(buildReq(fixture, kind, `${kind}-1`), ac.signal);\n expect(res.action).toBe(\"accept\");\n expect(res.done).toBe(true);\n expect(res.content).toBeUndefined();\n\n // The elicit response queued before should still be consumable.\n const elicitRes = await connector.send(elicitReq(fixture, `${kind}-elicit-1`), ac.signal);\n expect(elicitRes.action).toBe(\"accept\");\n expect(elicitRes.content).toEqual(sentinel);\n expect(elicitRes.requestId).toBe(`${kind}-elicit-1`);\n },\n opts.timeout\n );\n });\n}\n\nexport function notifyDisplayFastResolveBlock(\n opts: HumanConnectorConformanceOpts,\n fixture: ConformanceFixture,\n getHandle: () => HumanConnectorConformanceHandle\n): void {\n block(\"notify\", opts.capabilities.notify, \"notify.fastResolve\", opts, fixture, getHandle);\n block(\"display\", opts.capabilities.display, \"display.fastResolve\", opts, fixture, getHandle);\n}\n",
|
|
15
|
+
"/**\n * @license\n * Copyright 2026 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { IHumanRequest } from \"@workglow/util\";\nimport { describe, expect, it } from \"vitest\";\n\nimport { itExpectFail } from \"../../itExpectFail\";\nimport type {\n ConformanceFixture,\n HumanConnectorConformanceHandle,\n HumanConnectorConformanceOpts,\n} from \"../types\";\n\nfunction elicitReq(fixture: ConformanceFixture, requestId: string): IHumanRequest {\n return {\n requestId,\n targetHumanId: \"default\",\n kind: \"elicit\",\n message: \"Please confirm.\",\n contentSchema: fixture.elicitContentSchema,\n contentData: undefined,\n expectsResponse: true,\n mode: \"single\",\n metadata: undefined,\n };\n}\n\nfunction confirmReq(fixture: ConformanceFixture, requestId: string): IHumanRequest {\n return {\n requestId,\n targetHumanId: \"default\",\n kind: \"confirm\",\n message: fixture.confirmRequest.message,\n contentSchema: fixture.confirmRequest.contentSchema,\n contentData: fixture.confirmRequest.contentData,\n expectsResponse: true,\n mode: \"single\",\n metadata: undefined,\n };\n}\n\nexport function roundtripBlock(\n opts: HumanConnectorConformanceOpts,\n fixture: ConformanceFixture,\n getHandle: () => HumanConnectorConformanceHandle\n): void {\n const expectFails = new Set(opts.expectedFailures ?? []);\n const itAccept = expectFails.has(\"roundtrip.accept\") ? itExpectFail : it;\n const itDecline = expectFails.has(\"roundtrip.decline\") ? itExpectFail : it;\n const itCancel = expectFails.has(\"roundtrip.cancel\") ? itExpectFail : it;\n const itConfirmAccept = expectFails.has(\"roundtrip.confirm.accept\") ? itExpectFail : it;\n const itConfirmDecline = expectFails.has(\"roundtrip.confirm.decline\") ? itExpectFail : it;\n const itConfirmDetails = expectFails.has(\"roundtrip.confirm.details\") ? itExpectFail : it;\n const itConfirmNoContent = expectFails.has(\"roundtrip.confirm.noContent\") ? itExpectFail : it;\n\n describe.skipIf(!opts.capabilities.elicit)(\"Roundtrip elicit\", () => {\n itAccept(\n \"accept echoes requestId, returns done=true, surfaces content\",\n async () => {\n const { connector, script } = getHandle();\n script.push({\n requestId: \"ignored\",\n action: \"accept\",\n content: fixture.elicitAcceptContent,\n done: true,\n });\n const ac = new AbortController();\n const res = await connector.send(elicitReq(fixture, \"rt-accept-1\"), ac.signal);\n expect(res.requestId).toBe(\"rt-accept-1\");\n expect(res.action).toBe(\"accept\");\n expect(res.done).toBe(true);\n expect(res.content).toEqual(fixture.elicitAcceptContent);\n },\n opts.timeout\n );\n\n itDecline(\n \"decline surfaces with content=undefined, no throw\",\n async () => {\n const { connector, script } = getHandle();\n script.push({ requestId: \"x\", action: \"decline\", content: undefined, done: true });\n const ac = new AbortController();\n const res = await connector.send(elicitReq(fixture, \"rt-dec-1\"), ac.signal);\n expect(res.requestId).toBe(\"rt-dec-1\");\n expect(res.action).toBe(\"decline\");\n expect(res.done).toBe(true);\n expect(res.content).toBeUndefined();\n },\n opts.timeout\n );\n\n itCancel(\n \"cancel surfaces with content=undefined, no throw\",\n async () => {\n const { connector, script } = getHandle();\n script.push({ requestId: \"x\", action: \"cancel\", content: undefined, done: true });\n const ac = new AbortController();\n const res = await connector.send(elicitReq(fixture, \"rt-can-1\"), ac.signal);\n expect(res.requestId).toBe(\"rt-can-1\");\n expect(res.action).toBe(\"cancel\");\n expect(res.done).toBe(true);\n expect(res.content).toBeUndefined();\n },\n opts.timeout\n );\n });\n\n // Declaring `confirm: true` is a claim that a person decided. Without a\n // positive case the honesty block above proves nothing — it returns early\n // whenever the capability is true — so a connector that answered \"accept\"\n // without asking anyone would pass the whole suite.\n describe.skipIf(!opts.capabilities.confirm)(\"Roundtrip confirm\", () => {\n itConfirmAccept(\n \"a decision reaches the caller as the person's own answer\",\n async () => {\n const { connector, script } = getHandle();\n script.push({ requestId: \"x\", action: \"accept\", content: undefined, done: true });\n const ac = new AbortController();\n const res = await connector.send(confirmReq(fixture, \"rt-confirm-accept\"), ac.signal);\n expect(res.requestId).toBe(\"rt-confirm-accept\");\n expect(res.action).toBe(\"accept\");\n expect(res.done).toBe(true);\n },\n opts.timeout\n );\n\n itConfirmDecline(\n \"a refusal is a refusal, not an unanswered accept\",\n async () => {\n const { connector, script } = getHandle();\n script.push({ requestId: \"x\", action: \"decline\", content: undefined, done: true });\n const ac = new AbortController();\n const res = await connector.send(confirmReq(fixture, \"rt-confirm-decline\"), ac.signal);\n expect(res.requestId).toBe(\"rt-confirm-decline\");\n expect(res.action).toBe(\"decline\");\n expect(res.done).toBe(true);\n },\n opts.timeout\n );\n\n itConfirmNoContent(\n \"an accepted confirm answers with the decision and nothing else\",\n async () => {\n // A confirm's schema describes the action, so anything a connector\n // sends back under it is an answer to a form nobody asked. It reaches\n // the task's output ports (`HumanInputTask` spreads `content` there),\n // which is how an edit to the DESCRIPTION of an action becomes part of\n // what the graph does with it.\n const { connector, script } = getHandle();\n script.push({\n requestId: \"x\",\n action: \"accept\",\n content: { action: \"something else\" },\n done: true,\n });\n const ac = new AbortController();\n const res = await connector.send(confirmReq(fixture, \"rt-confirm-content\"), ac.signal);\n expect(res.action).toBe(\"accept\");\n expect(res.content).toBeUndefined();\n },\n opts.timeout\n );\n\n itConfirmDetails(\n \"the action's details reach the person deciding\",\n async () => {\n const { connector, script } = getHandle();\n script.push({ requestId: \"x\", action: \"decline\", content: undefined, done: true });\n const ac = new AbortController();\n await connector.send(confirmReq(fixture, \"rt-confirm-details\"), ac.signal);\n const received = script.received.at(-1);\n const shown = JSON.stringify({\n message: received?.message,\n contentData: received?.contentData,\n });\n for (const value of Object.values(fixture.confirmRequest.contentData ?? {})) {\n if (typeof value === \"string\") expect(shown).toContain(value);\n }\n },\n opts.timeout\n );\n });\n}\n"
|
|
16
|
+
],
|
|
17
|
+
"mappings": ";AAmCA,MAAM,OAAqC;AAAA,EACxB,YAA6B,CAAC;AAAA,EAC9B,SAAuB,CAAC;AAAA,EAMxB,oBAAqC,CAAC;AAAA,MAEnD,QAAQ,GAAiC;AAAA,IAC3C,OAAO,KAAK;AAAA;AAAA,EAGd,cAAc,CAAC,KAA0B;AAAA,IACvC,KAAK,UAAU,KAAK,GAAG;AAAA;AAAA,EAGzB,IAAI,CAAC,OAAgC;AAAA,IACnC,KAAK,OAAO,KAAK,EAAE,MAAM,aAAa,MAAM,CAAC;AAAA;AAAA,EAG/C,YAAY,GAAgD;AAAA,IAC1D,MAAM,MAAqB;AAAA,MACzB,MAAM;AAAA,MACN,UAAU,EAAE,OAAO,UAAU;AAAA,MAC7B,SAAS,EAAE,MAAM,MAAM;AAAA,MACvB,SAAS,CAAC;AAAA,MACV,WAAW,CAAC;AAAA,IACd;AAAA,IACA,KAAK,OAAO,KAAK,GAAG;AAAA,IACpB,KAAK,kBAAkB,KAAK,GAAG;AAAA,IAC/B,OAAO;AAAA,MACL,SAAS,CAAC,aAAa;AAAA,QACrB,IAAI,IAAI,QAAQ;AAAA,UAAM;AAAA,QACtB,IAAI,QAAQ,OAAO;AAAA,QACnB,IAAI,SAAS,QAAQ;AAAA,QACrB,WAAW,KAAK,IAAI;AAAA,UAAS,EAAE,QAAQ;AAAA,QACvC,IAAI,QAAQ,SAAS;AAAA,QACrB,IAAI,UAAU,SAAS;AAAA;AAAA,IAE3B;AAAA;AAAA,EAGF,KAAK,GAA2B;AAAA,IAC9B,OAAO,KAAK,OAAO,MAAM;AAAA;AAAA,EAG3B,KAAK,GAAS;AAAA,IACZ,WAAW,OAAO,KAAK,mBAAmB;AAAA,MACxC,IAAI,IAAI,QAAQ;AAAA,QAAM;AAAA,MACtB,IAAI,QAAQ,OAAO;AAAA,MACnB,MAAM,MAAM,IAAI,MAAM,8DAA8D;AAAA,MACpF,IAAI,OAAO;AAAA,MACX,WAAW,KAAK,IAAI;AAAA,QAAW,EAAE,GAAG;AAAA,MACpC,IAAI,UAAU,SAAS;AAAA,MACvB,IAAI,QAAQ,SAAS;AAAA,IACvB;AAAA,IACA,KAAK,kBAAkB,SAAS;AAAA,IAChC,KAAK,UAAU,SAAS;AAAA,IACxB,KAAK,OAAO,SAAS;AAAA;AAEzB;AAEA,SAAS,iBAAiB,CAAC,QAA4B;AAAA,EACrD,IAAI,OAAO,kBAAkB;AAAA,IAAO,OAAO,OAAO;AAAA,EAClD,MAAM,MAAM,IAAI,MAAM,2BAA2B;AAAA,EACjD,IAAI,OAAO;AAAA,EACX,OAAO;AAAA;AAGT,SAAS,aAAa,CAAC,KAAoB,QAA8C;AAAA,EACvF,OAAO,IAAI,QAAwB,CAAC,SAAS,WAAW;AAAA,IACtD,IAAI,OAAO,SAAS;AAAA,MAClB,OAAO,kBAAkB,MAAM,CAAC;AAAA,MAChC;AAAA,IACF;AAAA,IACA,IAAI,IAAI,QAAQ,QAAQ,IAAI,SAAS,UAAU,WAAW;AAAA,MACxD,QAAQ,IAAI,SAAS,KAAK;AAAA,MAC1B;AAAA,IACF;AAAA,IACA,MAAM,UAAU,MAAY;AAAA,MAC1B,OAAO,oBAAoB,SAAS,OAAO;AAAA,MAC3C,OAAO,kBAAkB,MAAM,CAAC;AAAA;AAAA,IAElC,OAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;AAAA,IACxD,IAAI,QAAQ,KAAK,CAAC,QAAQ;AAAA,MACxB,OAAO,oBAAoB,SAAS,OAAO;AAAA,MAC3C,QAAQ,GAAG;AAAA,KACZ;AAAA,IACD,IAAI,UAAU,KAAK,CAAC,QAAQ;AAAA,MAC1B,OAAO,oBAAoB,SAAS,OAAO;AAAA,MAC3C,OAAO,GAAG;AAAA,KACX;AAAA,GACF;AAAA;AAAA;AAGI,MAAM,mBAA8C;AAAA,EACxC;AAAA,EACA;AAAA,EACR;AAAA,EAMT,WAAW,CAAC,OAA+B,CAAC,GAAG;AAAA,IAC7C,KAAK,gBAAgB,KAAK,iBAAiB;AAAA,IAC3C,KAAK,UAAU,IAAI;AAAA,IACnB,IAAI,KAAK,oBAAoB,MAAM;AAAA,MACjC,KAAK,WAAW,CAAC,SAAS,WAAW,WAAW,KAAK,KAAK,SAAS,MAAM;AAAA,IAC3E;AAAA;AAAA,MAGE,MAAM,GAAuB;AAAA,IAC/B,OAAO,KAAK;AAAA;AAAA,OAGR,KAAI,CAAC,SAAwB,QAA8C;AAAA,IAC/E,IAAI,OAAO;AAAA,MAAS,MAAM,kBAAkB,MAAM;AAAA,IAClD,KAAK,QAAQ,eAAe,OAAO;AAAA,IAMnC,IAAI,QAAQ,SAAS,YAAY,QAAQ,SAAS,WAAW;AAAA,MAC3D,OAAO;AAAA,QACL,WAAW,QAAQ;AAAA,QACnB,QAAQ,KAAK;AAAA,QACb,SAAS;AAAA,QACT,MAAM;AAAA,MACR;AAAA,IACF;AAAA,IAEA,MAAM,QAAQ,KAAK,QAAQ,MAAM;AAAA,IACjC,IAAI,UAAU,WAAW;AAAA,MACvB,OAAO;AAAA,QACL,WAAW,QAAQ;AAAA,QACnB,QAAQ,KAAK;AAAA,QACb,SAAS;AAAA,QACT,MAAM;AAAA,MACR;AAAA,IACF;AAAA,IACA,IAAI,MAAM,SAAS,YAAY;AAAA,MAC7B,MAAM,MAAM,MAAM,cAAc,OAAO,MAAM;AAAA,MAC7C,OAAO,KAAK,MAAM,SAAS,GAAG;AAAA,IAChC;AAAA,IACA,MAAM,WAAW,OAAO,MAAM,UAAU,aAAa,MAAM,MAAM,MAAM,OAAO,IAAI,MAAM;AAAA,IACxF,OAAO,KAAK,MAAM,SAAS,QAAQ;AAAA;AAAA,EAU7B,KAAK,CAAC,SAAwB,UAA0C;AAAA,IAC9E,MAAM,iBAAiB,QAAQ,SAAS,YAAY,SAAS,WAAW;AAAA,IACxE,OAAO;AAAA,SACF;AAAA,MACH,WAAW,QAAQ;AAAA,MACnB,SAAS,iBAAiB,SAAS,UAAU;AAAA,IAC/C;AAAA;AAEJ;;AChMA,IAAM,eAA+B;AAAA,EACnC,MAAM;AAAA,EACN,YAAY;AAAA,IACV,UAAU;AAAA,MACR,MAAM;AAAA,MACN,OAAO;AAAA,MACP,aAAa;AAAA,IACf;AAAA,IACA,QAAQ,EAAE,MAAM,UAAU,OAAO,SAAS;AAAA,EAC5C;AAAA,EACA,UAAU,CAAC,UAAU;AAAA,EACrB,sBAAsB;AACxB;AAEA,IAAM,cAA8B;AAAA,EAClC,MAAM;AAAA,EACN,YAAY,CAAC;AAAA,EACb,sBAAsB;AACxB;AAEO,IAAM,oCAAwD;AAAA,EACnE,qBAAqB;AAAA,EACrB,qBAAqB,EAAE,UAAU,MAAM,QAAQ,aAAa;AAAA,EAC5D,eAAe;AAAA,IACb,SAAS;AAAA,IACT,eAAe;AAAA,IACf,aAAa,EAAE,OAAO,SAAS;AAAA,EACjC;AAAA,EACA,gBAAgB;AAAA,IACd,SAAS;AAAA,IACT,eAAe;AAAA,IACf,aAAa,EAAE,QAAQ,GAAG;AAAA,EAC5B;AAAA,EAGA,gBAAgB;AAAA,IACd,SAAS;AAAA,IACT,eAAe;AAAA,MACb,MAAM;AAAA,MACN,YAAY;AAAA,QACV,QAAQ,EAAE,MAAM,UAAU,OAAO,SAAS;AAAA,QAC1C,SAAS,EAAE,MAAM,UAAU,OAAO,UAAU;AAAA,MAC9C;AAAA,MACA,sBAAsB;AAAA,IACxB;AAAA,IACA,aAAa,EAAE,QAAQ,gBAAgB,SAAS,sCAAsC;AAAA,EACxF;AAAA,EACA,cAAc;AAChB;AAEO,SAAS,8BAA8B,CAC5C,UACoB;AAAA,EACpB,IAAI,CAAC;AAAA,IAAU,OAAO;AAAA,EACtB,OAAO,KAAK,sCAAsC,SAAS;AAAA;;AC1D7D,sDAA0C;;;ACC1C,iCAA2B;;;ACD3B,eAAS;AAoBT,IAAM,gBAAgB,IAAI,IAAI;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,IAAM,iBACJ;AAWF,IAAM,mBACJ;AAEK,SAAS,sBAAsB,CAAC,KAAuB;AAAA,EAC5D,MAAM,OAAO,IAAI;AAAA,EACjB,IAAI,UAAmB;AAAA,EACvB,OAAO,YAAY,aAAa,YAAY,QAAQ,CAAC,KAAK,IAAI,OAAO,GAAG;AAAA,IACtE,KAAK,IAAI,OAAO;AAAA,IAChB,IAAI,wBAAwB,OAAO;AAAA,MAAG,OAAO;AAAA,IAC7C,UAAU,QAAQ,OAAO;AAAA,EAC3B;AAAA,EACA,OAAO;AAAA;AAGT,SAAS,uBAAuB,CAAC,KAAuB;AAAA,EACtD,IAAI,OAAO,QAAQ;AAAA,IAAU,OAAO,iCAAiC,GAAG;AAAA,EAExE,IAAI,CAAC,OAAO,OAAO,QAAQ;AAAA,IAAU,OAAO;AAAA,EAE5C,MAAM,MAAM;AAAA,EACZ,MAAM,SAAS,cAAc,GAAG;AAAA,EAChC,IAAI,WAAW;AAAA,IAAK,OAAO;AAAA,EAE3B,MAAM,OAAO,UAAU,IAAI,IAAI,KAAK,UAAU,OAAO,IAAI,OAAO,MAAM,CAAC;AAAA,EACvE,MAAM,OAAO,UAAU,IAAI,IAAI,KAAK,UAAU,OAAO,IAAI,OAAO,MAAM,CAAC;AAAA,EACvE,IAAI,QAAQ,cAAc,IAAI,IAAI;AAAA,IAAG,OAAO;AAAA,EAC5C,IAAI,QAAQ,cAAc,IAAI,IAAI;AAAA,IAAG,OAAO;AAAA,EAE5C,MAAM,UAAU;AAAA,IACd,UAAU,IAAI,OAAO;AAAA,IACrB,UAAU,OAAO,IAAI,OAAO,SAAS,CAAC;AAAA,IACtC,iBAAiB,GAAG;AAAA,EACtB,EACG,OAAO,CAAC,SAAyB,OAAO,SAAS,QAAQ,EACzD,KAAK;AAAA,CAAI;AAAA,EACZ,OAAO,iCAAiC,OAAO;AAAA;AAGjD,SAAS,gCAAgC,CAAC,SAA0B;AAAA,EAClE,OAAO,eAAe,KAAK,OAAO,KAAK,iBAAiB,KAAK,OAAO;AAAA;AAGtE,SAAS,aAAa,CAAC,KAAkD;AAAA,EACvE,WAAW,OAAO,CAAC,UAAU,YAAY,GAAY;AAAA,IACnD,MAAM,QAAQ,IAAI;AAAA,IAClB,IAAI,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK;AAAA,MAAG,OAAO;AAAA,EAClE;AAAA,EACA,MAAM,eAAe,OAAO,IAAI,OAAO,QAAQ;AAAA,EAC/C,IAAI,OAAO,iBAAiB,YAAY,OAAO,SAAS,YAAY;AAAA,IAAG,OAAO;AAAA,EAC9E;AAAA;AAGF,SAAS,MAAM,CAAC,OAAgB,KAAsB;AAAA,EACpD,IAAI,CAAC,SAAS,OAAO,UAAU;AAAA,IAAU;AAAA,EACzC,OAAQ,MAAkC;AAAA;AAG5C,SAAS,SAAS,CAAC,OAAoC;AAAA,EACrD,OAAO,OAAO,UAAU,YAAY,MAAM,SAAS,IAAI,QAAQ;AAAA;AAGjE,SAAS,gBAAgB,CAAC,KAAsB;AAAA,EAC9C,IAAI,eAAe;AAAA,IAAO,OAAO,IAAI;AAAA,EACrC,OAAO,OAAO,GAAG;AAAA;AAGnB,SAAS,OAAO,CAAC,KAAuB;AAAA,EACtC,IAAI,CAAC,OAAO,OAAO,QAAQ;AAAA,IAAU;AAAA,EACrC,OAAQ,IAA4B;AAAA;AAG/B,SAAS,cAAc,CAAC,KAAsB;AAAA,EACnD,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,EAC/D,OAAO,qCAAqC;AAAA;AAG9C,IAAM,oBAAoB;AAE1B,SAAS,YAAY,CAAC,OAAwB;AAAA,EAC5C,OAAO,CAAC,wBAAwB,KAAK,MAAM,KAAK,CAAC;AAAA;AAkB5C,SAAS,yBAAyB,CACvC,MAA0C,QAAQ,KACzC;AAAA,EACT,MAAM,WAAW,IAAI;AAAA,EACrB,IAAI,aAAa,aAAa,SAAS,KAAK,MAAM;AAAA,IAAI,OAAO,aAAa,QAAQ;AAAA,EAClF,WAAW,OAAO,CAAC,MAAM,gBAAgB,GAAY;AAAA,IACnD,MAAM,QAAQ,IAAI;AAAA,IAClB,IAAI,UAAU,aAAa,MAAM,KAAK,MAAM,MAAM,aAAa,KAAK;AAAA,MAAG,OAAO;AAAA,EAChF;AAAA,EACA,OAAO;AAAA;AAQT,SAAS,wBAAwB,CAAC,KAAoB;AAAA,EACpD,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,EAC/D,QAAQ,MACN,4BAA4B;AAAA,IAC1B,gEAAgE,sCACpE;AAAA;AAGF,eAAsB,iBAAoB,CACxC,KACA,IACY;AAAA,EACZ,IAAI;AAAA,IACF,OAAO,MAAM,GAAG;AAAA,IAChB,OAAO,KAAK;AAAA,IACZ,IAAI,uBAAuB,GAAG,GAAG;AAAA,MAC/B,IAAI,CAAC,0BAA0B,GAAG;AAAA,QAChC,yBAAyB,GAAG;AAAA,QAC5B,MAAM;AAAA,MACR;AAAA,MACA,IAAI,KAAK,eAAe,GAAG,CAAC;AAAA,MAE5B;AAAA,IACF;AAAA,IACA,MAAM;AAAA;AAAA;AAUH,SAAS,yBAAyB,CACvC,IAC+B;AAAA,EAC/B,OAAO,eAAe,OAAO,IAAmB,MAAiC;AAAA,IAC/E,IAAI;AAAA,MACF,OAAO,MAAM,GAAG,MAAM,MAAM,IAAI;AAAA,MAChC,OAAO,KAAK;AAAA,MACZ,IAAI,CAAC,uBAAuB,GAAG;AAAA,QAAG,MAAM;AAAA,MACxC,IAAI,CAAC,0BAA0B,GAAG;AAAA,QAChC,yBAAyB,GAAG;AAAA,QAC5B,MAAM;AAAA,MACR;AAAA,MACA,MAAM,MAAM,KAAK;AAAA,MACjB,IAAI,OAAO,OAAO,IAAI,SAAS,YAAY;AAAA,QACzC,IAAI,KAAK,eAAe,GAAG,CAAC;AAAA,QAC5B;AAAA,MACF;AAAA,MAEA,QAAQ,KAAK,eAAe,GAAG,CAAC;AAAA,MAChC;AAAA;AAAA;AAAA;AAKN,SAAS,YAAY,CAAC,IAAwC;AAAA,EAC5D,OAAO,IAAI,MAAM,IAAI;AAAA,IACnB,KAAK,CAAC,QAAQ,SAAS,UAAqB;AAAA,MAC1C,MAAM,UAAU,SAAS,IAAI,CAAC,QAC5B,OAAO,QAAQ,aAAa,0BAA0B,GAAY,IAAI,GACxE;AAAA,MACA,MAAM,SAAS,QAAQ,MAAM,QAAQ,SAAS,OAAO;AAAA,MACrD,OAAO,OAAO,WAAW,aAAa,aAAa,MAAM,IAAI;AAAA;AAAA,IAE/D,GAAG,CAAC,QAAQ,MAAM,UAAU;AAAA,MAC1B,MAAM,QAAQ,QAAQ,IAAI,QAAQ,MAAM,QAAQ;AAAA,MAChD,IAAI,OAAO,UAAU,YAAY;AAAA,QAC/B,OAAO,aAAc,MAA2B,KAAK,MAAM,CAAC;AAAA,MAC9D;AAAA,MACA,OAAO;AAAA;AAAA,EAEX,CAAC;AAAA;AAOI,IAAM,KAAK,aAAa,QAAQ;;;AC/NhC,IAAM,eAAqB,CAAC,MAAM,IAAI,YAAY;AAAA,EACvD,MAAM,SAAU,GAAmC;AAAA,EACnD,IAAI,OAAO,WAAW,YAAY;AAAA,IAChC,OAAO,MAAM,IAAI,OAAO;AAAA,IACxB;AAAA,EACF;AAAA,EACA,GACE,GAAG,wBACH,YAAY;AAAA,IACV,IAAI,SAAS;AAAA,IACb,IAAI;AAAA,MACF,MAAM,GAAG;AAAA,MACT,SAAS;AAAA,MACT,OAAO,KAAK;AAAA,MAEZ,IAAI,uBAAuB,GAAG;AAAA,QAAG,MAAM;AAAA;AAAA,IAGzC,IAAI,QAAQ;AAAA,MACV,MAAM,IAAI,MACR,SAAS,2FACX;AAAA,IACF;AAAA,KAEF,OACF;AAAA;;;AF1BF,SAAS,YAAY,CAAC,KAAuB;AAAA,EAC3C,IAAI,CAAC,OAAO,OAAO,QAAQ;AAAA,IAAU,OAAO;AAAA,EAC5C,MAAM,IAAI;AAAA,EACV,OAAO,EAAE,SAAS,gBAAgB,SAAS,KAAK,EAAE,WAAW,EAAE;AAAA;AAGjE,SAAS,SAAS,CAAC,SAA6B,WAAkC;AAAA,EAChF,OAAO;AAAA,IACL;AAAA,IACA,eAAe;AAAA,IACf,MAAM;AAAA,IACN,SAAS;AAAA,IACT,eAAe,QAAQ;AAAA,IACvB,aAAa;AAAA,IACb,iBAAiB;AAAA,IACjB,MAAM;AAAA,IACN,UAAU;AAAA,EACZ;AAAA;AAGK,SAAS,UAAU,CACxB,MACA,SACA,WACM;AAAA,EACN,MAAM,cAAc,IAAI,IAAI,KAAK,oBAAoB,CAAC,CAAC;AAAA,EACvD,MAAM,WAAW,YAAY,IAAI,kBAAkB,IAAI,eAAe;AAAA,EACtE,MAAM,QAAQ,YAAY,IAAI,iBAAiB,IAAI,eAAe;AAAA,EAElE,SAAS,OAAO,CAAC,KAAK,aAAa,MAAM,EAAE,SAAS,MAAM;AAAA,IACxD,SACE,8DACA,YAAY;AAAA,MACV,QAAQ,cAAc,UAAU;AAAA,MAChC,MAAM,KAAK,IAAI;AAAA,MACf,GAAG,MAAM;AAAA,MACT,IAAI;AAAA,MACJ,IAAI;AAAA,QACF,MAAM,UAAU,KAAK,UAAU,SAAS,UAAU,GAAG,GAAG,MAAM;AAAA,QAC9D,OAAO,KAAK;AAAA,QACZ,SAAS;AAAA;AAAA,MAEX,OAAO,MAAM,EAAE,YAAY;AAAA,MAC3B,OAAO,aAAa,MAAM,CAAC,EAAE,KAAK,IAAI;AAAA,OAExC,KAAK,OACP;AAAA,IAEA,IAAI,KAAK,aAAa,gBAAgB;AAAA,MACpC,MACE,gEACA,YAAY;AAAA,QACV,QAAQ,WAAW,WAAW,UAAU;AAAA,QACxC,MAAM,SAAS,OAAO,aAAa;AAAA,QACnC,MAAM,KAAK,IAAI;AAAA,QACf,MAAM,QAAQ,KAAK,IAAI;AAAA,QACvB,MAAM,UAAU,UAAU,KAAK,UAAU,SAAS,UAAU,GAAG,GAAG,MAAM;AAAA,QACxE,WAAW,MAAM,GAAG,MAAM,GAAG,EAAE;AAAA,QAE/B,IAAI;AAAA,QACJ,IAAI;AAAA,UACF,MAAM;AAAA,UACN,OAAO,KAAK;AAAA,UACZ,SAAS;AAAA;AAAA,QAEX,MAAM,UAAU,KAAK,IAAI,IAAI;AAAA,QAC7B,OAAO,MAAM,EAAE,YAAY;AAAA,QAC3B,OAAO,aAAa,MAAM,CAAC,EAAE,KAAK,IAAI;AAAA,QACtC,OAAO,OAAO,EAAE,aAAa,QAAQ,eAAe,IAAI,IAAI;AAAA,QAE5D,OAAO,MACL,OAAO,QAAQ;AAAA,UACb,WAAW;AAAA,UACX,QAAQ;AAAA,UACR,SAAS;AAAA,UACT,MAAM;AAAA,QACR,CAAC,CACH,EAAE,IAAI,QAAQ;AAAA,SAEhB,KAAK,OACP;AAAA,IACF;AAAA,GACD;AAAA;;;AG3FH,qBAAS,qBAAU,eAAQ;AAS3B,SAAS,QAAQ,CACf,SACA,MACA,WACe;AAAA,EACf,MAAM,OACJ,SAAS,WACL,EAAE,SAAS,UAAU,eAAe,QAAQ,qBAAqB,aAAa,UAAU,IACxF,SAAS,WACP,QAAQ,gBACR,SAAS,YACP,QAAQ,iBACR,QAAQ;AAAA,EAClB,OAAO;AAAA,IACL;AAAA,IACA,eAAe;AAAA,IACf;AAAA,IACA,SAAS,KAAK;AAAA,IACd,eAAe,KAAK;AAAA,IACpB,aAAa,KAAK;AAAA,IAClB,iBAAiB,SAAS,YAAY,SAAS;AAAA,IAC/C,MAAM;AAAA,IACN,UAAU;AAAA,EACZ;AAAA;AAGK,SAAS,sBAAsB,CACpC,MACA,SACA,WACM;AAAA,EACN,MAAM,cAAc,IAAI,IAAI,KAAK,oBAAoB,CAAC,CAAC;AAAA,EACvD,MAAM,OAAO,YAAY,IAAI,mBAAmB,IAAI,eAAe;AAAA,EAEnE,UAAS,sBAAsB,MAAM;AAAA,IACnC,KACE,oEACA,MAAM;AAAA,MACJ,IAAI,KAAK,aAAa;AAAA,QAAW;AAAA,MACjC,QAAQ,cAAc,UAAU;AAAA,MAChC,QAAO,UAAU,QAAQ,EAAE,cAAc;AAAA,OAE3C,KAAK,OACP;AAAA,IAEA,WAAW,QAAQ,CAAC,UAAU,WAAW,UAAU,SAAS,GAAY;AAAA,MACtE,KACE,GAAG,qGACH,YAAY;AAAA,QACV,IAAI,KAAK,aAAa;AAAA,UAAO;AAAA,QAC7B,QAAQ,cAAc,UAAU;AAAA,QAChC,MAAM,KAAK,IAAI;AAAA,QACf,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ,IAAI;AAAA,UACF,MAAM,MAAM,UAAU,KAAK,SAAS,SAAS,MAAM,OAAO,MAAM,GAAG,GAAG,MAAM;AAAA,UAC5E,OAAO,KAAK;AAAA,UACZ,SAAS;AAAA;AAAA,QAEX,IAAI,QAAQ;AAAA,UACV,QAAO,MAAM,EAAE,YAAY;AAAA,UAC3B;AAAA,QACF;AAAA,QACA,QAAO,GAAG,EAAE,YAAY;AAAA,QACxB,QAAO,IAAK,MAAM,EAAE,IAAI,KAAK,QAAQ;AAAA,SAEvC,KAAK,OACP;AAAA,IACF;AAAA,GACD;AAAA;;;AC9EH,qBAAS,qBAAU,eAAQ;AAS3B,SAAS,UAAS,CAAC,SAA6B,WAAkC;AAAA,EAChF,OAAO;AAAA,IACL;AAAA,IACA,eAAe;AAAA,IACf,MAAM;AAAA,IAIN,SAAS,kBAAkB;AAAA,IAC3B,eAAe,QAAQ;AAAA,IACvB,aAAa;AAAA,IACb,iBAAiB;AAAA,IACjB,MAAM;AAAA,IACN,UAAU;AAAA,EACZ;AAAA;AAGK,SAAS,wBAAwB,CACtC,MACA,SACA,WACM;AAAA,EACN,MAAM,cAAc,IAAI,IAAI,KAAK,oBAAoB,CAAC,CAAC;AAAA,EACvD,MAAM,eAAe,YAAY,IAAI,sBAAsB,IAAI,eAAe;AAAA,EAE9E,UAAS,OAAO,CAAC,KAAK,aAAa,cAAc,CAAC,KAAK,aAAa,MAAM,EACxE,wBACA,MAAM;AAAA,IACJ,aACE,wEACA,YAAY;AAAA,MACV,QAAQ,WAAW,WAAW,UAAU;AAAA,MAIxC,MAAM,iBAAiB,CAAC,YAA4B;AAAA,QAClD,MAAM,IAAI,QAAQ,MAAM,uBAAuB;AAAA,QAC/C,OAAO,IAAI,EAAE,KAAK;AAAA;AAAA,MAEpB,OAAO,KAAK,CAAC,QAAwB;AAAA,QACnC,MAAM,MAAM,eAAe,IAAI,OAAO;AAAA,QACtC,OAAO;AAAA,UACL,WAAW,IAAI;AAAA,UACf,QAAQ;AAAA,UACR,SAAS,EAAE,UAAU,MAAM,QAAQ,IAAI;AAAA,UACvC,MAAM;AAAA,QACR;AAAA,OACD;AAAA,MACD,OAAO,KAAK,CAAC,QAAwB;AAAA,QACnC,MAAM,MAAM,eAAe,IAAI,OAAO;AAAA,QACtC,OAAO;AAAA,UACL,WAAW,IAAI;AAAA,UACf,QAAQ;AAAA,UACR,SAAS,EAAE,UAAU,MAAM,QAAQ,IAAI;AAAA,UACvC,MAAM;AAAA,QACR;AAAA,OACD;AAAA,MAED,MAAM,KAAK,IAAI;AAAA,MACf,MAAM,KAAK,UAAU,KAAK,WAAU,SAAS,QAAQ,GAAG,GAAG,MAAM;AAAA,MACjE,MAAM,KAAK,UAAU,KAAK,WAAU,SAAS,QAAQ,GAAG,GAAG,MAAM;AAAA,MACjE,OAAO,GAAG,KAAK,MAAM,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;AAAA,MAEzC,QAAO,EAAE,SAAS,EAAE,KAAK,QAAQ;AAAA,MACjC,QAAQ,EAAE,QAA+B,MAAM,EAAE,KAAK,QAAQ;AAAA,MAC9D,QAAO,EAAE,SAAS,EAAE,KAAK,QAAQ;AAAA,MACjC,QAAQ,EAAE,QAA+B,MAAM,EAAE,KAAK,QAAQ;AAAA,OAEhE,KAAK,OACP;AAAA,GAEJ;AAAA;;;AChFF,qBAAS,qBAAU,eAAQ;AAS3B,SAAS,UAAS,CAChB,SACA,WACA,MACe;AAAA,EACf,OAAO;AAAA,IACL;AAAA,IACA,eAAe;AAAA,IACf,MAAM;AAAA,IACN,SAAS;AAAA,IACT,eAAe,QAAQ;AAAA,IACvB,aAAa;AAAA,IACb,iBAAiB;AAAA,IACjB;AAAA,IACA,UAAU;AAAA,EACZ;AAAA;AAGF,SAAS,aAAY,CAAC,KAAuB;AAAA,EAC3C,IAAI,CAAC,OAAO,OAAO,QAAQ;AAAA,IAAU,OAAO;AAAA,EAC5C,MAAM,IAAI;AAAA,EACV,OAAO,EAAE,SAAS,gBAAgB,SAAS,KAAK,EAAE,WAAW,EAAE;AAAA;AAG1D,SAAS,sBAAsB,CACpC,MACA,SACA,WACM;AAAA,EACN,MAAM,cAAc,IAAI,IAAI,KAAK,oBAAoB,CAAC,CAAC;AAAA,EACvD,MAAM,OAAO,YAAY,IAAI,oBAAoB,IAAI,eAAe;AAAA,EAEpE,UAAS,OAAO,CAAC,KAAK,aAAa,aAAa,CAAC,KAAK,aAAa,MAAM,EACvE,uBACA,MAAM;AAAA,IACJ,KACE,yEACA,YAAY;AAAA,MACV,QAAQ,WAAW,WAAW,UAAU;AAAA,MACxC,OAAO,KAAK;AAAA,QACV,WAAW;AAAA,QACX,QAAQ;AAAA,QACR,SAAS,EAAE,UAAU,OAAO,QAAQ,QAAQ;AAAA,QAC5C,MAAM;AAAA,MACR,CAAC;AAAA,MACD,OAAO,KAAK;AAAA,QACV,WAAW;AAAA,QACX,QAAQ;AAAA,QACR,SAAS,EAAE,UAAU,MAAM,QAAQ,QAAQ;AAAA,QAC3C,MAAM;AAAA,MACR,CAAC;AAAA,MAED,MAAM,KAAK,IAAI;AAAA,MACf,MAAM,MAAM,WAAU,SAAS,QAAQ,YAAY;AAAA,MACnD,MAAM,QAAQ,MAAM,UAAU,KAAK,KAAK,GAAG,MAAM;AAAA,MACjD,QAAO,MAAM,IAAI,EAAE,KAAK,KAAK;AAAA,MAE7B,QAAO,OAAO,UAAU,QAAQ,EAAE,KAAK,UAAU;AAAA,MACjD,MAAM,SAAS,MAAM,UAAU,SAAU,KAAK,OAAO,GAAG,MAAM;AAAA,MAC9D,QAAO,OAAO,IAAI,EAAE,KAAK,IAAI;AAAA,MAC7B,QAAO,OAAO,OAAO,EAAE,QAAQ,EAAE,UAAU,MAAM,QAAQ,QAAQ,CAAC;AAAA,OAEpE,KAAK,OACP;AAAA,IAEA,IAAI,KAAK,aAAa,gBAAgB;AAAA,MACpC,KACE,4CACA,YAAY;AAAA,QACV,QAAQ,WAAW,WAAW,UAAU;AAAA,QACxC,OAAO,KAAK;AAAA,UACV,WAAW;AAAA,UACX,QAAQ;AAAA,UACR,SAAS,EAAE,UAAU,OAAO,QAAQ,QAAQ;AAAA,UAC5C,MAAM;AAAA,QACR,CAAC;AAAA,QACD,OAAO,aAAa;AAAA,QACpB,MAAM,KAAK,IAAI;AAAA,QACf,MAAM,MAAM,WAAU,SAAS,QAAQ,YAAY;AAAA,QACnD,MAAM,QAAQ,MAAM,UAAU,KAAK,KAAK,GAAG,MAAM;AAAA,QACjD,QAAO,MAAM,IAAI,EAAE,KAAK,KAAK;AAAA,QAC7B,MAAM,UAAU,UAAU,SAAU,KAAK,OAAO,GAAG,MAAM;AAAA,QACzD,WAAW,MAAM,GAAG,MAAM,GAAG,EAAE;AAAA,QAC/B,IAAI;AAAA,QACJ,IAAI;AAAA,UACF,MAAM;AAAA,UACN,OAAO,KAAK;AAAA,UACZ,SAAS;AAAA;AAAA,QAEX,QAAO,MAAM,EAAE,YAAY;AAAA,QAC3B,QAAO,cAAa,MAAM,CAAC,EAAE,KAAK,IAAI;AAAA,SAExC,KAAK,OACP;AAAA,IACF;AAAA,GAEJ;AAAA;;;ACzGF,qBAAS,qBAAU,eAAQ;AAU3B,SAAS,SAAQ,CACf,SACA,MACA,WACe;AAAA,EACf,MAAM,OAAO,SAAS,WAAW,QAAQ,gBAAgB,QAAQ;AAAA,EACjE,OAAO;AAAA,IACL;AAAA,IACA,eAAe;AAAA,IACf;AAAA,IACA,SAAS,KAAK;AAAA,IACd,eAAe,KAAK;AAAA,IACpB,aAAa,KAAK;AAAA,IAClB,iBAAiB;AAAA,IACjB,MAAM;AAAA,IACN,UAAU;AAAA,EACZ;AAAA;AAGF,SAAS,UAAS,CAAC,SAA6B,WAAkC;AAAA,EAChF,OAAO;AAAA,IACL;AAAA,IACA,eAAe;AAAA,IACf,MAAM;AAAA,IACN,SAAS;AAAA,IACT,eAAe,QAAQ;AAAA,IACvB,aAAa;AAAA,IACb,iBAAiB;AAAA,IACjB,MAAM;AAAA,IACN,UAAU;AAAA,EACZ;AAAA;AAGF,SAAS,KAAK,CACZ,MACA,SACA,QACA,MACA,SACA,WACM;AAAA,EACN,MAAM,cAAc,IAAI,IAAI,KAAK,oBAAoB,CAAC,CAAC;AAAA,EACvD,MAAM,OAAO,YAAY,IAAI,MAAM,IAAI,eAAe;AAAA,EAEtD,UAAS,OAAO,CAAC,WAAW,CAAC,KAAK,aAAa,MAAM,EAAE,GAAG,qBAAqB,MAAM;AAAA,IACnF,KACE,GAAG,+FACH,YAAY;AAAA,MACV,QAAQ,WAAW,WAAW,UAAU;AAAA,MAKxC,MAAM,WAAW,EAAE,UAAU,MAAM,QAAQ,WAAW;AAAA,MACtD,OAAO,KAAK,EAAE,WAAW,WAAW,QAAQ,UAAU,SAAS,UAAU,MAAM,KAAK,CAAC;AAAA,MAErF,MAAM,KAAK,IAAI;AAAA,MACf,MAAM,MAAM,MAAM,UAAU,KAAK,UAAS,SAAS,MAAM,GAAG,QAAQ,GAAG,GAAG,MAAM;AAAA,MAChF,QAAO,IAAI,MAAM,EAAE,KAAK,QAAQ;AAAA,MAChC,QAAO,IAAI,IAAI,EAAE,KAAK,IAAI;AAAA,MAC1B,QAAO,IAAI,OAAO,EAAE,cAAc;AAAA,MAGlC,MAAM,YAAY,MAAM,UAAU,KAAK,WAAU,SAAS,GAAG,eAAe,GAAG,GAAG,MAAM;AAAA,MACxF,QAAO,UAAU,MAAM,EAAE,KAAK,QAAQ;AAAA,MACtC,QAAO,UAAU,OAAO,EAAE,QAAQ,QAAQ;AAAA,MAC1C,QAAO,UAAU,SAAS,EAAE,KAAK,GAAG,eAAe;AAAA,OAErD,KAAK,OACP;AAAA,GACD;AAAA;AAGI,SAAS,6BAA6B,CAC3C,MACA,SACA,WACM;AAAA,EACN,MAAM,UAAU,KAAK,aAAa,QAAQ,sBAAsB,MAAM,SAAS,SAAS;AAAA,EACxF,MAAM,WAAW,KAAK,aAAa,SAAS,uBAAuB,MAAM,SAAS,SAAS;AAAA;;;ACzF7F,qBAAS,qBAAU,eAAQ;AAS3B,SAAS,UAAS,CAAC,SAA6B,WAAkC;AAAA,EAChF,OAAO;AAAA,IACL;AAAA,IACA,eAAe;AAAA,IACf,MAAM;AAAA,IACN,SAAS;AAAA,IACT,eAAe,QAAQ;AAAA,IACvB,aAAa;AAAA,IACb,iBAAiB;AAAA,IACjB,MAAM;AAAA,IACN,UAAU;AAAA,EACZ;AAAA;AAGF,SAAS,UAAU,CAAC,SAA6B,WAAkC;AAAA,EACjF,OAAO;AAAA,IACL;AAAA,IACA,eAAe;AAAA,IACf,MAAM;AAAA,IACN,SAAS,QAAQ,eAAe;AAAA,IAChC,eAAe,QAAQ,eAAe;AAAA,IACtC,aAAa,QAAQ,eAAe;AAAA,IACpC,iBAAiB;AAAA,IACjB,MAAM;AAAA,IACN,UAAU;AAAA,EACZ;AAAA;AAGK,SAAS,cAAc,CAC5B,MACA,SACA,WACM;AAAA,EACN,MAAM,cAAc,IAAI,IAAI,KAAK,oBAAoB,CAAC,CAAC;AAAA,EACvD,MAAM,WAAW,YAAY,IAAI,kBAAkB,IAAI,eAAe;AAAA,EACtE,MAAM,YAAY,YAAY,IAAI,mBAAmB,IAAI,eAAe;AAAA,EACxE,MAAM,WAAW,YAAY,IAAI,kBAAkB,IAAI,eAAe;AAAA,EACtE,MAAM,kBAAkB,YAAY,IAAI,0BAA0B,IAAI,eAAe;AAAA,EACrF,MAAM,mBAAmB,YAAY,IAAI,2BAA2B,IAAI,eAAe;AAAA,EACvF,MAAM,mBAAmB,YAAY,IAAI,2BAA2B,IAAI,eAAe;AAAA,EACvF,MAAM,qBAAqB,YAAY,IAAI,6BAA6B,IAAI,eAAe;AAAA,EAE3F,UAAS,OAAO,CAAC,KAAK,aAAa,MAAM,EAAE,oBAAoB,MAAM;AAAA,IACnE,SACE,gEACA,YAAY;AAAA,MACV,QAAQ,WAAW,WAAW,UAAU;AAAA,MACxC,OAAO,KAAK;AAAA,QACV,WAAW;AAAA,QACX,QAAQ;AAAA,QACR,SAAS,QAAQ;AAAA,QACjB,MAAM;AAAA,MACR,CAAC;AAAA,MACD,MAAM,KAAK,IAAI;AAAA,MACf,MAAM,MAAM,MAAM,UAAU,KAAK,WAAU,SAAS,aAAa,GAAG,GAAG,MAAM;AAAA,MAC7E,QAAO,IAAI,SAAS,EAAE,KAAK,aAAa;AAAA,MACxC,QAAO,IAAI,MAAM,EAAE,KAAK,QAAQ;AAAA,MAChC,QAAO,IAAI,IAAI,EAAE,KAAK,IAAI;AAAA,MAC1B,QAAO,IAAI,OAAO,EAAE,QAAQ,QAAQ,mBAAmB;AAAA,OAEzD,KAAK,OACP;AAAA,IAEA,UACE,qDACA,YAAY;AAAA,MACV,QAAQ,WAAW,WAAW,UAAU;AAAA,MACxC,OAAO,KAAK,EAAE,WAAW,KAAK,QAAQ,WAAW,SAAS,WAAW,MAAM,KAAK,CAAC;AAAA,MACjF,MAAM,KAAK,IAAI;AAAA,MACf,MAAM,MAAM,MAAM,UAAU,KAAK,WAAU,SAAS,UAAU,GAAG,GAAG,MAAM;AAAA,MAC1E,QAAO,IAAI,SAAS,EAAE,KAAK,UAAU;AAAA,MACrC,QAAO,IAAI,MAAM,EAAE,KAAK,SAAS;AAAA,MACjC,QAAO,IAAI,IAAI,EAAE,KAAK,IAAI;AAAA,MAC1B,QAAO,IAAI,OAAO,EAAE,cAAc;AAAA,OAEpC,KAAK,OACP;AAAA,IAEA,SACE,oDACA,YAAY;AAAA,MACV,QAAQ,WAAW,WAAW,UAAU;AAAA,MACxC,OAAO,KAAK,EAAE,WAAW,KAAK,QAAQ,UAAU,SAAS,WAAW,MAAM,KAAK,CAAC;AAAA,MAChF,MAAM,KAAK,IAAI;AAAA,MACf,MAAM,MAAM,MAAM,UAAU,KAAK,WAAU,SAAS,UAAU,GAAG,GAAG,MAAM;AAAA,MAC1E,QAAO,IAAI,SAAS,EAAE,KAAK,UAAU;AAAA,MACrC,QAAO,IAAI,MAAM,EAAE,KAAK,QAAQ;AAAA,MAChC,QAAO,IAAI,IAAI,EAAE,KAAK,IAAI;AAAA,MAC1B,QAAO,IAAI,OAAO,EAAE,cAAc;AAAA,OAEpC,KAAK,OACP;AAAA,GACD;AAAA,EAMD,UAAS,OAAO,CAAC,KAAK,aAAa,OAAO,EAAE,qBAAqB,MAAM;AAAA,IACrE,gBACE,4DACA,YAAY;AAAA,MACV,QAAQ,WAAW,WAAW,UAAU;AAAA,MACxC,OAAO,KAAK,EAAE,WAAW,KAAK,QAAQ,UAAU,SAAS,WAAW,MAAM,KAAK,CAAC;AAAA,MAChF,MAAM,KAAK,IAAI;AAAA,MACf,MAAM,MAAM,MAAM,UAAU,KAAK,WAAW,SAAS,mBAAmB,GAAG,GAAG,MAAM;AAAA,MACpF,QAAO,IAAI,SAAS,EAAE,KAAK,mBAAmB;AAAA,MAC9C,QAAO,IAAI,MAAM,EAAE,KAAK,QAAQ;AAAA,MAChC,QAAO,IAAI,IAAI,EAAE,KAAK,IAAI;AAAA,OAE5B,KAAK,OACP;AAAA,IAEA,iBACE,oDACA,YAAY;AAAA,MACV,QAAQ,WAAW,WAAW,UAAU;AAAA,MACxC,OAAO,KAAK,EAAE,WAAW,KAAK,QAAQ,WAAW,SAAS,WAAW,MAAM,KAAK,CAAC;AAAA,MACjF,MAAM,KAAK,IAAI;AAAA,MACf,MAAM,MAAM,MAAM,UAAU,KAAK,WAAW,SAAS,oBAAoB,GAAG,GAAG,MAAM;AAAA,MACrF,QAAO,IAAI,SAAS,EAAE,KAAK,oBAAoB;AAAA,MAC/C,QAAO,IAAI,MAAM,EAAE,KAAK,SAAS;AAAA,MACjC,QAAO,IAAI,IAAI,EAAE,KAAK,IAAI;AAAA,OAE5B,KAAK,OACP;AAAA,IAEA,mBACE,kEACA,YAAY;AAAA,MAMV,QAAQ,WAAW,WAAW,UAAU;AAAA,MACxC,OAAO,KAAK;AAAA,QACV,WAAW;AAAA,QACX,QAAQ;AAAA,QACR,SAAS,EAAE,QAAQ,iBAAiB;AAAA,QACpC,MAAM;AAAA,MACR,CAAC;AAAA,MACD,MAAM,KAAK,IAAI;AAAA,MACf,MAAM,MAAM,MAAM,UAAU,KAAK,WAAW,SAAS,oBAAoB,GAAG,GAAG,MAAM;AAAA,MACrF,QAAO,IAAI,MAAM,EAAE,KAAK,QAAQ;AAAA,MAChC,QAAO,IAAI,OAAO,EAAE,cAAc;AAAA,OAEpC,KAAK,OACP;AAAA,IAEA,iBACE,kDACA,YAAY;AAAA,MACV,QAAQ,WAAW,WAAW,UAAU;AAAA,MACxC,OAAO,KAAK,EAAE,WAAW,KAAK,QAAQ,WAAW,SAAS,WAAW,MAAM,KAAK,CAAC;AAAA,MACjF,MAAM,KAAK,IAAI;AAAA,MACf,MAAM,UAAU,KAAK,WAAW,SAAS,oBAAoB,GAAG,GAAG,MAAM;AAAA,MACzE,MAAM,WAAW,OAAO,SAAS,GAAG,EAAE;AAAA,MACtC,MAAM,QAAQ,KAAK,UAAU;AAAA,QAC3B,SAAS,UAAU;AAAA,QACnB,aAAa,UAAU;AAAA,MACzB,CAAC;AAAA,MACD,WAAW,SAAS,OAAO,OAAO,QAAQ,eAAe,eAAe,CAAC,CAAC,GAAG;AAAA,QAC3E,IAAI,OAAO,UAAU;AAAA,UAAU,QAAO,KAAK,EAAE,UAAU,KAAK;AAAA,MAC9D;AAAA,OAEF,KAAK,OACP;AAAA,GACD;AAAA;;;ARpKI,SAAS,4BAA4B,CAAC,MAA2C;AAAA,EACtF,UAAS,OAAO,KAAK,IAAI,EAAE,gCAAgC,KAAK,QAAQ,MAAM;AAAA,IAC5E,IAAI;AAAA,IACJ,MAAM,YAAY,MAAuC;AAAA,MACvD,IAAI,CAAC;AAAA,QAAQ,MAAM,IAAI,MAAM,oCAAoC;AAAA,MACjE,OAAO;AAAA;AAAA,IAGT,UAAU,YAAY;AAAA,MACpB,SAAS,MAAM,KAAK,QAAQ;AAAA,OAC3B,KAAK,OAAO;AAAA,IAEf,WAAW,MAAM;AAAA,MAEf,QAAQ,OAAO,MAAM;AAAA,KACtB;AAAA,IAED,SAAS,YAAY;AAAA,MACnB,IAAI;AAAA,QAAQ,MAAM,OAAO,QAAQ;AAAA,KAClC;AAAA,IAED,MAAM,UAAU,+BAA+B,KAAK,OAAO;AAAA,IAE3D,eAAe,MAAM,SAAS,SAAS;AAAA,IACvC,WAAW,MAAM,SAAS,SAAS;AAAA,IACnC,yBAAyB,MAAM,SAAS,SAAS;AAAA,IACjD,8BAA8B,MAAM,SAAS,SAAS;AAAA,IACtD,uBAAuB,MAAM,SAAS,SAAS;AAAA,IAC/C,uBAAuB,MAAM,SAAS,SAAS;AAAA,GAChD;AAAA;",
|
|
18
|
+
"debugId": "1244A5B943D10CD864756E2164756E21",
|
|
19
|
+
"names": []
|
|
20
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Steven Roussey <sroussey@gmail.com>
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
type ItFn = (name: string, fn: () => Promise<void> | void, timeout?: number) => void;
|
|
7
|
+
/**
|
|
8
|
+
* Cross-runner polyfill for `it.fails`. Vitest exposes it natively; bun's
|
|
9
|
+
* native `bun test` runner does not. Under bun we wrap the body in a
|
|
10
|
+
* try/catch and assert that it threw — so a passing-when-expected-fail
|
|
11
|
+
* test still surfaces as a CI failure (signalling "remove from
|
|
12
|
+
* expectedFailures").
|
|
13
|
+
*/
|
|
14
|
+
export declare const itExpectFail: ItFn;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
// src/creditExhaustedSkip.ts
|
|
2
|
+
import { it as vitestIt } from "vitest";
|
|
3
|
+
var BILLING_CODES = new Set([
|
|
4
|
+
"insufficient_quota",
|
|
5
|
+
"billing_hard_limit_reached",
|
|
6
|
+
"insufficient_credits",
|
|
7
|
+
"insufficient_balance"
|
|
8
|
+
]);
|
|
9
|
+
var CREDIT_MESSAGE = /insufficient credits|not enough credits|no credits remaining|out of credits|insufficient[\s_-]balance|balance is (?:too low|insufficient)|exceeded[\s\S]{0,80}credits|monthly included credits|payment required|exceeded your current quota|check your plan and billing|billing_hard_limit/i;
|
|
10
|
+
var HTTP_402_MESSAGE = /\bHTTP\/?\d?(?:\.\d)?\s*402\b|\bstatus(?:\s*code)?\s*[:=]\s*402\b|\berror code:?\s*402\b|(?:^|:\s+)402\s+[a-z]/im;
|
|
11
|
+
function isCreditExhaustedError(err) {
|
|
12
|
+
const seen = new Set;
|
|
13
|
+
let current = err;
|
|
14
|
+
while (current !== undefined && current !== null && !seen.has(current)) {
|
|
15
|
+
seen.add(current);
|
|
16
|
+
if (matchesCreditExhaustion(current))
|
|
17
|
+
return true;
|
|
18
|
+
current = causeOf(current);
|
|
19
|
+
}
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
function matchesCreditExhaustion(err) {
|
|
23
|
+
if (typeof err === "string")
|
|
24
|
+
return messageLooksLikeCreditExhaustion(err);
|
|
25
|
+
if (!err || typeof err !== "object")
|
|
26
|
+
return false;
|
|
27
|
+
const rec = err;
|
|
28
|
+
const status = numericStatus(rec);
|
|
29
|
+
if (status === 402)
|
|
30
|
+
return true;
|
|
31
|
+
const code = stringish(rec.code) ?? stringish(nested(rec.error, "code"));
|
|
32
|
+
const type = stringish(rec.type) ?? stringish(nested(rec.error, "type"));
|
|
33
|
+
if (code && BILLING_CODES.has(code))
|
|
34
|
+
return true;
|
|
35
|
+
if (type && BILLING_CODES.has(type))
|
|
36
|
+
return true;
|
|
37
|
+
const message = [
|
|
38
|
+
stringish(rec.message),
|
|
39
|
+
stringish(nested(rec.error, "message")),
|
|
40
|
+
stringifyUnknown(err)
|
|
41
|
+
].filter((part) => typeof part === "string").join(`
|
|
42
|
+
`);
|
|
43
|
+
return messageLooksLikeCreditExhaustion(message);
|
|
44
|
+
}
|
|
45
|
+
function messageLooksLikeCreditExhaustion(message) {
|
|
46
|
+
return CREDIT_MESSAGE.test(message) || HTTP_402_MESSAGE.test(message);
|
|
47
|
+
}
|
|
48
|
+
function numericStatus(rec) {
|
|
49
|
+
for (const key of ["status", "statusCode"]) {
|
|
50
|
+
const value = rec[key];
|
|
51
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
const nestedStatus = nested(rec.error, "status");
|
|
55
|
+
if (typeof nestedStatus === "number" && Number.isFinite(nestedStatus))
|
|
56
|
+
return nestedStatus;
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
function nested(value, key) {
|
|
60
|
+
if (!value || typeof value !== "object")
|
|
61
|
+
return;
|
|
62
|
+
return value[key];
|
|
63
|
+
}
|
|
64
|
+
function stringish(value) {
|
|
65
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
66
|
+
}
|
|
67
|
+
function stringifyUnknown(err) {
|
|
68
|
+
if (err instanceof Error)
|
|
69
|
+
return err.message;
|
|
70
|
+
return String(err);
|
|
71
|
+
}
|
|
72
|
+
function causeOf(err) {
|
|
73
|
+
if (!err || typeof err !== "object")
|
|
74
|
+
return;
|
|
75
|
+
return err.cause;
|
|
76
|
+
}
|
|
77
|
+
function creditSkipNote(err) {
|
|
78
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
79
|
+
return `skipped: provider out of credits (${message})`;
|
|
80
|
+
}
|
|
81
|
+
var SKIP_OVERRIDE_ENV = "WORKGLOW_CREDIT_EXHAUSTED_SKIP";
|
|
82
|
+
function isTruthyFlag(value) {
|
|
83
|
+
return !/^(?:0|false|off|no)$/i.test(value.trim());
|
|
84
|
+
}
|
|
85
|
+
function creditExhaustionSkipsTest(env = process.env) {
|
|
86
|
+
const override = env[SKIP_OVERRIDE_ENV];
|
|
87
|
+
if (override !== undefined && override.trim() !== "")
|
|
88
|
+
return isTruthyFlag(override);
|
|
89
|
+
for (const key of ["CI", "GITHUB_ACTIONS"]) {
|
|
90
|
+
const value = env[key];
|
|
91
|
+
if (value !== undefined && value.trim() !== "" && isTruthyFlag(value))
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
function reportLocalCreditFailure(err) {
|
|
97
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
98
|
+
console.error(`provider out of credits (${message})
|
|
99
|
+
` + ` Failing because this is not CI. Top the account up, or set ${SKIP_OVERRIDE_ENV}=1 to skip instead.`);
|
|
100
|
+
}
|
|
101
|
+
async function runWithCreditSkip(ctx, fn) {
|
|
102
|
+
try {
|
|
103
|
+
return await fn();
|
|
104
|
+
} catch (err) {
|
|
105
|
+
if (isCreditExhaustedError(err)) {
|
|
106
|
+
if (!creditExhaustionSkipsTest()) {
|
|
107
|
+
reportLocalCreditFailure(err);
|
|
108
|
+
throw err;
|
|
109
|
+
}
|
|
110
|
+
ctx.skip(creditSkipNote(err));
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
throw err;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function wrapTestBodyForCreditSkip(fn) {
|
|
117
|
+
return async function wrapped(...args) {
|
|
118
|
+
try {
|
|
119
|
+
return await fn.apply(this, args);
|
|
120
|
+
} catch (err) {
|
|
121
|
+
if (!isCreditExhaustedError(err))
|
|
122
|
+
throw err;
|
|
123
|
+
if (!creditExhaustionSkipsTest()) {
|
|
124
|
+
reportLocalCreditFailure(err);
|
|
125
|
+
throw err;
|
|
126
|
+
}
|
|
127
|
+
const ctx = args[0];
|
|
128
|
+
if (ctx && typeof ctx.skip === "function") {
|
|
129
|
+
ctx.skip(creditSkipNote(err));
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
console.warn(creditSkipNote(err));
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function wrapCallable(fn) {
|
|
138
|
+
return new Proxy(fn, {
|
|
139
|
+
apply(target, thisArg, argArray) {
|
|
140
|
+
const wrapped = argArray.map((arg) => typeof arg === "function" ? wrapTestBodyForCreditSkip(arg) : arg);
|
|
141
|
+
const result = Reflect.apply(target, thisArg, wrapped);
|
|
142
|
+
return typeof result === "function" ? wrapCallable(result) : result;
|
|
143
|
+
},
|
|
144
|
+
get(target, prop, receiver) {
|
|
145
|
+
const value = Reflect.get(target, prop, receiver);
|
|
146
|
+
if (typeof value === "function") {
|
|
147
|
+
return wrapCallable(value.bind(target));
|
|
148
|
+
}
|
|
149
|
+
return value;
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
var it = wrapCallable(vitestIt);
|
|
154
|
+
|
|
155
|
+
// src/itExpectFail.ts
|
|
156
|
+
var itExpectFail = (name, fn, timeout) => {
|
|
157
|
+
const native = it.fails;
|
|
158
|
+
if (typeof native === "function") {
|
|
159
|
+
native(name, fn, timeout);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
it(`${name} [expected-fail]`, async () => {
|
|
163
|
+
let passed = false;
|
|
164
|
+
try {
|
|
165
|
+
await fn();
|
|
166
|
+
passed = true;
|
|
167
|
+
} catch (err) {
|
|
168
|
+
if (isCreditExhaustedError(err))
|
|
169
|
+
throw err;
|
|
170
|
+
}
|
|
171
|
+
if (passed) {
|
|
172
|
+
throw new Error(`Test "${name}" was marked as expected-fail but passed. Remove its name from opts.expectedFailures.`);
|
|
173
|
+
}
|
|
174
|
+
}, timeout);
|
|
175
|
+
};
|
|
176
|
+
export {
|
|
177
|
+
itExpectFail
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
//# debugId=534B4D46D7FD8AF264756E2164756E21
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/creditExhaustedSkip.ts", "../src/itExpectFail.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * @license\n * Copyright 2026 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { it as vitestIt } from \"vitest\";\n\n/**\n * Live provider tests treat \"the account is out of credits\" as a billing\n * condition rather than a product bug. A key being present is not the same as\n * the account being able to pay; HuggingFace / OpenRouter 402s, Anthropic's\n * credit-balance error, DeepSeek's `Insufficient Balance`, and OpenAI's\n * `insufficient_quota` are all billing.\n *\n * Rate limits (429 + `rate_limit_exceeded`) are NOT this: those are transient\n * and the existing retry policy handles them.\n *\n * Where it skips and where it fails is a deliberate split\n * ({@link creditExhaustionSkipsTest}): on CI it skips for every provider,\n * because a red build tells a reviewer nothing they can act on; locally it\n * fails, because the developer running the suite is the one who can top the\n * account up. `WORKGLOW_CREDIT_EXHAUSTED_SKIP=1` forces the skip locally\n * (and `=0` forces the failure on CI).\n */\n\nconst BILLING_CODES = new Set([\n \"insufficient_quota\",\n \"billing_hard_limit_reached\",\n \"insufficient_credits\",\n \"insufficient_balance\",\n]);\n\nconst CREDIT_MESSAGE =\n /insufficient credits|not enough credits|no credits remaining|out of credits|insufficient[\\s_-]balance|balance is (?:too low|insufficient)|exceeded[\\s\\S]{0,80}credits|monthly included credits|payment required|exceeded your current quota|check your plan and billing|billing_hard_limit/i;\n\n/**\n * A 402 that survives only as text. `classifyProviderError` rebuilds the error\n * as a `PermanentJobError` carrying the message and no `cause`, so by the time\n * a live test sees a DeepSeek billing failure the numeric `status` is gone and\n * the OpenAI-SDK message shape (`402 Insufficient Balance`) is all that is\n * left. Anchored to an HTTP-shaped position — start of a line, or after a\n * summary line's colon — so prose that merely contains the number\n * (\"Item 402(c)\") is not scavenged as a status.\n */\nconst HTTP_402_MESSAGE =\n /\\bHTTP\\/?\\d?(?:\\.\\d)?\\s*402\\b|\\bstatus(?:\\s*code)?\\s*[:=]\\s*402\\b|\\berror code:?\\s*402\\b|(?:^|:\\s+)402\\s+[a-z]/im;\n\nexport function isCreditExhaustedError(err: unknown): boolean {\n const seen = new Set<unknown>();\n let current: unknown = err;\n while (current !== undefined && current !== null && !seen.has(current)) {\n seen.add(current);\n if (matchesCreditExhaustion(current)) return true;\n current = causeOf(current);\n }\n return false;\n}\n\nfunction matchesCreditExhaustion(err: unknown): boolean {\n if (typeof err === \"string\") return messageLooksLikeCreditExhaustion(err);\n\n if (!err || typeof err !== \"object\") return false;\n\n const rec = err as Record<string, unknown>;\n const status = numericStatus(rec);\n if (status === 402) return true;\n\n const code = stringish(rec.code) ?? stringish(nested(rec.error, \"code\"));\n const type = stringish(rec.type) ?? stringish(nested(rec.error, \"type\"));\n if (code && BILLING_CODES.has(code)) return true;\n if (type && BILLING_CODES.has(type)) return true;\n\n const message = [\n stringish(rec.message),\n stringish(nested(rec.error, \"message\")),\n stringifyUnknown(err),\n ]\n .filter((part): part is string => typeof part === \"string\")\n .join(\"\\n\");\n return messageLooksLikeCreditExhaustion(message);\n}\n\nfunction messageLooksLikeCreditExhaustion(message: string): boolean {\n return CREDIT_MESSAGE.test(message) || HTTP_402_MESSAGE.test(message);\n}\n\nfunction numericStatus(rec: Record<string, unknown>): number | undefined {\n for (const key of [\"status\", \"statusCode\"] as const) {\n const value = rec[key];\n if (typeof value === \"number\" && Number.isFinite(value)) return value;\n }\n const nestedStatus = nested(rec.error, \"status\");\n if (typeof nestedStatus === \"number\" && Number.isFinite(nestedStatus)) return nestedStatus;\n return undefined;\n}\n\nfunction nested(value: unknown, key: string): unknown {\n if (!value || typeof value !== \"object\") return undefined;\n return (value as Record<string, unknown>)[key];\n}\n\nfunction stringish(value: unknown): string | undefined {\n return typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\nfunction stringifyUnknown(err: unknown): string {\n if (err instanceof Error) return err.message;\n return String(err);\n}\n\nfunction causeOf(err: unknown): unknown {\n if (!err || typeof err !== \"object\") return undefined;\n return (err as { cause?: unknown }).cause;\n}\n\nexport function creditSkipNote(err: unknown): string {\n const message = err instanceof Error ? err.message : String(err);\n return `skipped: provider out of credits (${message})`;\n}\n\nconst SKIP_OVERRIDE_ENV = \"WORKGLOW_CREDIT_EXHAUSTED_SKIP\";\n\nfunction isTruthyFlag(value: string): boolean {\n return !/^(?:0|false|off|no)$/i.test(value.trim());\n}\n\n/**\n * Whether a billing failure skips the test or fails it.\n *\n * CI skips: nobody watching a build can top an account up, and one exhausted\n * key would otherwise turn every provider suite red for a change that touched\n * none of them — so the leniency is provider-agnostic by construction, keyed on\n * the environment rather than on which vendor ran out.\n *\n * Locally it fails, loudly, because the developer running the suite IS the\n * person who can act on it — and a silently green local run is how an\n * exhausted account goes unnoticed until CI is the only thing exercising the\n * provider at all.\n *\n * `WORKGLOW_CREDIT_EXHAUSTED_SKIP` overrides in both directions.\n */\nexport function creditExhaustionSkipsTest(\n env: Record<string, string | undefined> = process.env\n): boolean {\n const override = env[SKIP_OVERRIDE_ENV];\n if (override !== undefined && override.trim() !== \"\") return isTruthyFlag(override);\n for (const key of [\"CI\", \"GITHUB_ACTIONS\"] as const) {\n const value = env[key];\n if (value !== undefined && value.trim() !== \"\" && isTruthyFlag(value)) return true;\n }\n return false;\n}\n\n/**\n * The local-failure path. Rethrows the provider's own error unchanged — its\n * identity and stack are what makes the failure diagnosable — after one line\n * saying why it was not skipped and how to skip it.\n */\nfunction reportLocalCreditFailure(err: unknown): void {\n const message = err instanceof Error ? err.message : String(err);\n console.error(\n `provider out of credits (${message})\\n` +\n ` Failing because this is not CI. Top the account up, or set ${SKIP_OVERRIDE_ENV}=1 to skip instead.`\n );\n}\n\nexport async function runWithCreditSkip<T>(\n ctx: { skip: (note?: string) => void },\n fn: () => Promise<T> | T\n): Promise<T> {\n try {\n return await fn();\n } catch (err) {\n if (isCreditExhaustedError(err)) {\n if (!creditExhaustionSkipsTest()) {\n reportLocalCreditFailure(err);\n throw err;\n }\n ctx.skip(creditSkipNote(err));\n // vitest's skip() is `never` (throws). Mocks used in unit tests return.\n return undefined as T;\n }\n throw err;\n }\n}\n\n/**\n * Wraps one test body. This is the seam every conformance assertion actually\n * runs through (the exported {@link it} proxies each callback through it), so\n * the CI-skips / locally-fails policy is applied here rather than at each\n * call site.\n */\nexport function wrapTestBodyForCreditSkip(\n fn: (...args: never[]) => unknown\n): (...args: never[]) => unknown {\n return async function wrapped(this: unknown, ...args: never[]): Promise<unknown> {\n try {\n return await fn.apply(this, args);\n } catch (err) {\n if (!isCreditExhaustedError(err)) throw err;\n if (!creditExhaustionSkipsTest()) {\n reportLocalCreditFailure(err);\n throw err;\n }\n const ctx = args[0] as { skip?: (note?: string) => void } | undefined;\n if (ctx && typeof ctx.skip === \"function\") {\n ctx.skip(creditSkipNote(err));\n return undefined;\n }\n // bun:test has no TestContext.skip; passing is \"don't fail the test\".\n console.warn(creditSkipNote(err));\n return undefined;\n }\n };\n}\n\nfunction wrapCallable(fn: CallableFunction): CallableFunction {\n return new Proxy(fn, {\n apply(target, thisArg, argArray: unknown[]) {\n const wrapped = argArray.map((arg) =>\n typeof arg === \"function\" ? wrapTestBodyForCreditSkip(arg as never) : arg\n );\n const result = Reflect.apply(target, thisArg, wrapped);\n return typeof result === \"function\" ? wrapCallable(result) : result;\n },\n get(target, prop, receiver) {\n const value = Reflect.get(target, prop, receiver);\n if (typeof value === \"function\") {\n return wrapCallable((value as CallableFunction).bind(target));\n }\n return value;\n },\n });\n}\n\n/**\n * Drop-in `it` for live provider tests: a credit/billing error skips the test on\n * CI and fails it locally (see {@link creditExhaustionSkipsTest}).\n */\nexport const it = wrapCallable(vitestIt) as typeof vitestIt;\n",
|
|
6
|
+
"/**\n * @license\n * Copyright 2026 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { isCreditExhaustedError, it } from \"./creditExhaustedSkip\";\n\ntype ItFn = (name: string, fn: () => Promise<void> | void, timeout?: number) => void;\n\n/**\n * Cross-runner polyfill for `it.fails`. Vitest exposes it natively; bun's\n * native `bun test` runner does not. Under bun we wrap the body in a\n * try/catch and assert that it threw — so a passing-when-expected-fail\n * test still surfaces as a CI failure (signalling \"remove from\n * expectedFailures\").\n */\nexport const itExpectFail: ItFn = (name, fn, timeout) => {\n const native = (it as unknown as { fails?: ItFn }).fails;\n if (typeof native === \"function\") {\n native(name, fn, timeout);\n return;\n }\n it(\n `${name} [expected-fail]`,\n async () => {\n let passed = false;\n try {\n await fn();\n passed = true;\n } catch (err) {\n // Billing exhaustion is not the adapter bug this marker records.\n if (isCreditExhaustedError(err)) throw err;\n // expected; the test was supposed to fail\n }\n if (passed) {\n throw new Error(\n `Test \"${name}\" was marked as expected-fail but passed. Remove its name from opts.expectedFailures.`\n );\n }\n },\n timeout\n );\n};\n"
|
|
7
|
+
],
|
|
8
|
+
"mappings": "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",
|
|
9
|
+
"debugId": "534B4D46D7FD8AF264756E2164756E21",
|
|
10
|
+
"names": []
|
|
11
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Steven Roussey <sroussey@gmail.com>
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { type MigrationContractHandle, type MigrationRunnerContractOpts } from "../types";
|
|
7
|
+
export declare function appliesAndRecordsBlock<DB>(opts: MigrationRunnerContractOpts<DB>, getHandle: () => MigrationContractHandle<DB>): 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 MigrationContractHandle, type MigrationRunnerContractOpts } from "../types";
|
|
7
|
+
/**
|
|
8
|
+
* Concurrent `run()` calls against the same backing database — possibly
|
|
9
|
+
* across different runner instances — must execute each migration's `up()`
|
|
10
|
+
* exactly once and converge appliedVersions on a single row per
|
|
11
|
+
* `(component, version)`.
|
|
12
|
+
*
|
|
13
|
+
* Adapters that rely solely on the bookkeeping PK to resolve races (race
|
|
14
|
+
* losers see 23505 and treat as success) will run `up()` multiple times
|
|
15
|
+
* before that resolution, and should mark this assertion as
|
|
16
|
+
* `expectedFailures: ["concurrentRunsSerialize"]`.
|
|
17
|
+
*/
|
|
18
|
+
export declare function concurrentRunsSerializeBlock<DB>(opts: MigrationRunnerContractOpts<DB>, getHandle: () => MigrationContractHandle<DB>): void;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Steven Roussey <sroussey@gmail.com>
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { type MigrationContractHandle, type MigrationRunnerContractOpts } from "../types";
|
|
7
|
+
export declare function ensureBookkeepingIdempotentBlock<DB>(opts: MigrationRunnerContractOpts<DB>, getHandle: () => MigrationContractHandle<DB>): void;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Steven Roussey <sroussey@gmail.com>
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { type MigrationContractHandle, type MigrationRunnerContractOpts } from "../types";
|
|
7
|
+
/**
|
|
8
|
+
* A migration whose `up()` creates a probe schema element and then throws
|
|
9
|
+
* must leave no trace of the probe after the runner rejects. Backends
|
|
10
|
+
* without atomic DDL (e.g. SQLite without an explicit transaction wrapper)
|
|
11
|
+
* retain the partial schema and should mark this assertion as
|
|
12
|
+
* `expectedFailures: ["failedMigrationLeavesNoPartialSchema"]`.
|
|
13
|
+
*/
|
|
14
|
+
export declare function failedMigrationLeavesNoPartialSchemaBlock<DB>(opts: MigrationRunnerContractOpts<DB>, getHandle: () => MigrationContractHandle<DB>): void;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Steven Roussey <sroussey@gmail.com>
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { type MigrationContractHandle, type MigrationRunnerContractOpts } from "../types";
|
|
7
|
+
export declare function failedMigrationNotRecordedBlock<DB>(opts: MigrationRunnerContractOpts<DB>, getHandle: () => MigrationContractHandle<DB>): void;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Steven Roussey <sroussey@gmail.com>
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { type MigrationContractHandle, type MigrationRunnerContractOpts } from "../types";
|
|
7
|
+
export declare function idempotentRunBlock<DB>(opts: MigrationRunnerContractOpts<DB>, getHandle: () => MigrationContractHandle<DB>): void;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Steven Roussey <sroussey@gmail.com>
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { type MigrationContractHandle, type MigrationRunnerContractOpts } from "../types";
|
|
7
|
+
export declare function incrementalApplicationBlock<DB>(opts: MigrationRunnerContractOpts<DB>, getHandle: () => MigrationContractHandle<DB>): void;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Steven Roussey <sroussey@gmail.com>
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import type { MigrationRunnerContractOpts } from "./types";
|
|
7
|
+
export declare function runMigrationRunnerContract<DB>(opts: MigrationRunnerContractOpts<DB>): void;
|
|
8
|
+
export { createMigrationCallRecorder } from "./types";
|
|
9
|
+
export type { BuildMigrationFn, BuildMigrationOptions, MigrationCallRecorder, MigrationContractAssertion, MigrationContractHandle, MigrationRunnerContractOpts, } from "./types";
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Steven Roussey <sroussey@gmail.com>
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import type { IMigration, IMigrationRunner } from "@workglow/storage";
|
|
7
|
+
/**
|
|
8
|
+
* Records each invocation of a migration's `up()` callback. The contract
|
|
9
|
+
* suite uses this to verify which migrations actually ran (orthogonal to
|
|
10
|
+
* the runner's own bookkeeping table).
|
|
11
|
+
*
|
|
12
|
+
* Caveat: the recorder reflects whether `up()` *executed*, not whether
|
|
13
|
+
* its effects *persisted*. For backends that wrap `up()` in a transaction
|
|
14
|
+
* (Postgres BEGIN/ROLLBACK; IndexedDB upgrade tx.abort), a migration whose
|
|
15
|
+
* `up()` records and then throws has `recorder.calls` containing that
|
|
16
|
+
* version even though the bookkeeping insert and any DDL were rolled back.
|
|
17
|
+
* Use `runner.appliedVersions(component)` to check what *persisted*; use
|
|
18
|
+
* the recorder to check what *ran*.
|
|
19
|
+
*/
|
|
20
|
+
export interface MigrationCallRecorder {
|
|
21
|
+
readonly calls: ReadonlyArray<{
|
|
22
|
+
readonly component: string;
|
|
23
|
+
readonly version: number;
|
|
24
|
+
}>;
|
|
25
|
+
readonly record: (component: string, version: number) => void;
|
|
26
|
+
readonly clear: () => void;
|
|
27
|
+
}
|
|
28
|
+
export declare function createMigrationCallRecorder(): MigrationCallRecorder;
|
|
29
|
+
export interface BuildMigrationOptions {
|
|
30
|
+
/** When set, the migration's `up()` throws synchronously. */
|
|
31
|
+
readonly fail?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* When set, `up()` first creates a probe schema element with this name
|
|
34
|
+
* (a table for SQL backends, an object store for IDB) and then throws.
|
|
35
|
+
* Used by the partial-schema assertion to check whether the runner rolls
|
|
36
|
+
* DDL back on failure.
|
|
37
|
+
*/
|
|
38
|
+
readonly probeName?: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Adapter-supplied factory that constructs a migration of the right
|
|
42
|
+
* shape for the underlying DB (Postgres pool, SQLite database, or
|
|
43
|
+
* IndexedDb upgrade context). The returned migration's `up()` calls the
|
|
44
|
+
* recorder unless `opts.fail` is true.
|
|
45
|
+
*/
|
|
46
|
+
export type BuildMigrationFn<DB> = (component: string, version: number, recorder: MigrationCallRecorder, opts?: BuildMigrationOptions) => IMigration<DB>;
|
|
47
|
+
export interface MigrationContractHandle<DB> {
|
|
48
|
+
readonly createRunner: () => Promise<IMigrationRunner<DB>>;
|
|
49
|
+
readonly buildMigration: BuildMigrationFn<DB>;
|
|
50
|
+
/**
|
|
51
|
+
* Returns true iff a probe schema element with the given name currently
|
|
52
|
+
* exists in the backing database. Required by the
|
|
53
|
+
* `failedMigrationLeavesNoPartialSchema` assertion. Implementations
|
|
54
|
+
* inspect the relevant catalog (`information_schema.tables`,
|
|
55
|
+
* `sqlite_master`, `db.objectStoreNames`).
|
|
56
|
+
*/
|
|
57
|
+
readonly probeExists: (name: string) => Promise<boolean>;
|
|
58
|
+
readonly dispose: () => Promise<void>;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Closed string-literal union of contract assertions adapters can mark as
|
|
62
|
+
* known failing. Adding a new assertion to the suite must extend this
|
|
63
|
+
* union — that way a typo in `expectedFailures` becomes a TS error rather
|
|
64
|
+
* than a silently-ignored entry.
|
|
65
|
+
*/
|
|
66
|
+
export type MigrationContractAssertion = "appliesAndRecords" | "idempotentRun" | "incrementalApplication" | "failedMigrationNotRecorded" | "ensureBookkeepingIdempotent" | "concurrentRunsSerialize" | "failedMigrationLeavesNoPartialSchema";
|
|
67
|
+
export interface MigrationRunnerContractOpts<DB> {
|
|
68
|
+
readonly name: string;
|
|
69
|
+
readonly skip?: boolean;
|
|
70
|
+
readonly timeout: number;
|
|
71
|
+
readonly factory: () => Promise<MigrationContractHandle<DB>>;
|
|
72
|
+
/**
|
|
73
|
+
* Names of contract assertions currently broken in this adapter; each
|
|
74
|
+
* named test wraps with `itExpectFail`. The string-literal union catches
|
|
75
|
+
* typos at compile time.
|
|
76
|
+
*/
|
|
77
|
+
readonly expectedFailures?: ReadonlyArray<MigrationContractAssertion>;
|
|
78
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Steven Roussey <sroussey@gmail.com>
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Conformance suite for `IMigrationRunner` — what a migration records, what a
|
|
8
|
+
* failed one leaves behind, and what a second run does.
|
|
9
|
+
*/
|
|
10
|
+
export * from "./storage-migrations/runMigrationRunnerContract";
|
|
11
|
+
export * from "./storage-migrations/types";
|
|
12
|
+
export * from "./storage-migrations/assertions/appliesAndRecords";
|
|
13
|
+
export * from "./storage-migrations/assertions/concurrentRunsSerialize";
|
|
14
|
+
export * from "./storage-migrations/assertions/ensureBookkeepingIdempotent";
|
|
15
|
+
export * from "./storage-migrations/assertions/failedMigrationLeavesNoPartialSchema";
|
|
16
|
+
export * from "./storage-migrations/assertions/failedMigrationNotRecorded";
|
|
17
|
+
export * from "./storage-migrations/assertions/idempotentRun";
|
|
18
|
+
export * from "./storage-migrations/assertions/incrementalApplication";
|