@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,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/browser-context/ConformanceMockContext.ts", "../src/browser-context/fixtures.ts", "../src/browser-context/runIBrowserContextConformance.ts", "../src/browser-context/assertions/ariaRoundTrip.ts", "../src/browser-context/assertions/itExpectFail.ts", "../src/browser-context/assertions/capabilityHonesty.ts", "../src/browser-context/assertions/networkIntrospection.ts", "../src/browser-context/assertions/tabsLifecycle.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 AccessibilityNode,\n AccessibilityTree,\n AriaRole,\n BrowserConnectOptions,\n ClickOptions,\n ConsoleMessage,\n DialogAction,\n DialogInfo,\n DownloadOptions,\n DownloadResult,\n ElementRef,\n IBrowserContext,\n NavigateOptions,\n NetworkRequest,\n ScreenshotOptions,\n SnapshotOptions,\n TabInfo,\n WaitOptions,\n} from \"@workglow/browser-control/task\";\n\ninterface PageState {\n readonly tabId: string;\n url: string;\n title: string;\n ariaButtons: ReadonlyArray<{ readonly idx: number; readonly name: string }>;\n sentinelClicked: string;\n network: NetworkRequest[];\n console: ConsoleMessage[];\n}\n\n/**\n * Parses the conformance fixture page out of a `data:text/html,...` URL by\n * scraping aria-label values from `<button ... aria-label=\"...\">` tags. This\n * is intentionally narrow — only the conformance fixture page is supported.\n */\nfunction parseFixturePage(url: string): {\n readonly title: string;\n readonly ariaButtons: ReadonlyArray<{ readonly idx: number; readonly name: string }>;\n readonly networkMarker: string | undefined;\n readonly consoleMarker: string | undefined;\n} {\n let html = \"\";\n if (url.startsWith(\"data:text/html,\")) {\n html = decodeURIComponent(url.slice(\"data:text/html,\".length));\n }\n const titleMatch = /<title>([^<]*)<\\/title>/.exec(html);\n const buttons: { idx: number; name: string }[] = [];\n const buttonRe = /<button[^>]*aria-label=\"([^\"]*)\"[^>]*>/g;\n let m: RegExpExecArray | null;\n let idx = 0;\n while ((m = buttonRe.exec(html)) !== null) {\n const decoded = m[1]\n .replace(/"/g, '\"')\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/&/g, \"&\");\n buttons.push({ idx, name: decoded });\n idx++;\n }\n const fetchMatch = /fetch\\(\"data:text\\/plain,([^\"]+)\"/.exec(html);\n const consoleMatch = /console\\.log\\(\"([^\"]+)\"\\)/.exec(html);\n return {\n title: titleMatch ? titleMatch[1] : \"\",\n ariaButtons: buttons,\n networkMarker: fetchMatch?.[1],\n consoleMarker: consoleMatch?.[1],\n };\n}\n\nexport class ConformanceMockContext implements IBrowserContext {\n private connected = false;\n private nextTabSeq = 1;\n private nextRefSeq = 1;\n private pages: PageState[] = [];\n private activeTabId: string | undefined;\n /** ref → { tabId, name } produced by snapshot(). */\n private refMap = new Map<string, { tabId: string; name: string; idx: number }>();\n\n // networkRequests / consoleMessages are intentionally absent (undefined)\n // when the corresponding capability flag is false. The shim wires this\n // class with capabilities.{networkRequests,consoleMessages}: false, so\n // these properties stay undefined to satisfy capability honesty (negative).\n\n // -- Lifecycle ------------------------------------------------------------\n async connect(_options?: BrowserConnectOptions): Promise<void> {\n this.connected = true;\n if (this.pages.length === 0) {\n const page = this.makePage(\"about:blank\", \"\");\n this.pages.push(page);\n this.activeTabId = page.tabId;\n }\n }\n async disconnect(): Promise<void> {\n this.connected = false;\n this.pages = [];\n this.activeTabId = undefined;\n this.refMap.clear();\n }\n isConnected(): boolean {\n return this.connected;\n }\n\n private makePage(url: string, title: string): PageState {\n return {\n tabId: `mock-tab-${this.nextTabSeq++}`,\n url,\n title,\n ariaButtons: [],\n sentinelClicked: \"\",\n network: [],\n console: [],\n };\n }\n\n private active(): PageState {\n const p = this.pages.find((p) => p.tabId === this.activeTabId);\n if (!p) throw new Error(\"ConformanceMockContext: no active tab\");\n return p;\n }\n\n // -- Navigation -----------------------------------------------------------\n async navigate(url: string, _options?: NavigateOptions): Promise<void> {\n const page = this.active();\n page.url = url;\n const parsed = parseFixturePage(url);\n page.title = parsed.title;\n page.ariaButtons = parsed.ariaButtons;\n page.sentinelClicked = \"\";\n if (parsed.networkMarker) {\n page.network.push({\n url: `data:text/plain,${parsed.networkMarker}`,\n method: \"GET\",\n status: 200,\n headers: {},\n });\n }\n if (parsed.consoleMarker) {\n page.console.push({ type: \"log\", text: parsed.consoleMarker });\n }\n }\n async goBack(_options?: NavigateOptions): Promise<void> {}\n async goForward(_options?: NavigateOptions): Promise<void> {}\n async reload(_options?: NavigateOptions): Promise<void> {}\n async currentUrl(): Promise<string> {\n return this.active().url;\n }\n async title(): Promise<string> {\n return this.active().title;\n }\n\n // -- Accessibility --------------------------------------------------------\n async snapshot(_options?: SnapshotOptions): Promise<AccessibilityTree> {\n const page = this.active();\n const children: AccessibilityNode[] = page.ariaButtons.map((b) => {\n const ref: ElementRef = `e${this.nextRefSeq++}`;\n this.refMap.set(ref, { tabId: page.tabId, name: b.name, idx: b.idx });\n return { ref, role: \"button\", name: b.name };\n });\n const rootRef: ElementRef = `e${this.nextRefSeq++}`;\n return {\n root: { ref: rootRef, role: \"document\", name: page.title, children },\n yaml: children.map((c) => `- button [${c.ref}]: ${c.name}`).join(\"\\n\"),\n };\n }\n\n // -- Element interaction --------------------------------------------------\n async click(ref: ElementRef, _options?: ClickOptions): Promise<void> {\n const entry = this.refMap.get(ref);\n if (!entry) throw new Error(`ConformanceMockContext: unknown ref \"${ref}\"`);\n const page = this.pages.find((p) => p.tabId === entry.tabId);\n if (!page) throw new Error(`ConformanceMockContext: tab gone for ref \"${ref}\"`);\n page.sentinelClicked = String(entry.idx);\n }\n async fill(_ref: ElementRef, _value: string, _options?: WaitOptions): Promise<void> {}\n async selectOption(\n _ref: ElementRef,\n _values: string | readonly string[],\n _options?: WaitOptions\n ): Promise<void> {}\n async hover(_ref: ElementRef, _options?: WaitOptions): Promise<void> {}\n\n async clickByRole(role: AriaRole, name: string, _options?: ClickOptions): Promise<void> {\n if (role !== \"button\") {\n throw new Error(`ConformanceMockContext: unsupported role \"${role}\"`);\n }\n const page = this.active();\n const btn = page.ariaButtons.find((b) => b.name === name);\n if (!btn) throw new Error(`ConformanceMockContext: no button named ${JSON.stringify(name)}`);\n page.sentinelClicked = String(btn.idx);\n }\n async fillByLabel(_label: string, _value: string, _options?: WaitOptions): Promise<void> {}\n\n // -- Content extraction ---------------------------------------------------\n async content(): Promise<string> {\n return \"\";\n }\n async innerHTML(_ref: ElementRef): Promise<string> {\n return \"\";\n }\n async textContent(ref: ElementRef): Promise<string | null> {\n const entry = this.refMap.get(ref);\n return entry ? entry.name : null;\n }\n async attribute(ref: ElementRef, name: string): Promise<string | null> {\n // Only the synthetic sentinel ref carries data-clicked. Button refs and\n // other refs do not have per-element attributes modeled — they get null,\n // matching what a real backend would return for an unrelated attribute.\n if (name === \"data-clicked\" && ref.startsWith(\"sentinel-\")) {\n const tabId = ref.slice(\"sentinel-\".length);\n const page = this.pages.find((p) => p.tabId === tabId);\n return page ? page.sentinelClicked : null;\n }\n return null;\n }\n async querySelector(selector: string): Promise<ElementRef | null> {\n if (selector === \"#sentinel\") {\n // Return a stable sentinel ref scoped to the active tab so that\n // attribute() can resolve the page's sentinelClicked value.\n const page = this.active();\n return `sentinel-${page.tabId}` as ElementRef;\n }\n return null;\n }\n async querySelectorAll(_selector: string): Promise<readonly ElementRef[]> {\n return [];\n }\n\n async evaluate<T>(expression: string): Promise<T> {\n // Narrow support: the assertion suite reads sentinel via attribute(), not\n // evaluate(), but provide a readiness check used by waitForIdle.\n if (expression.includes(\"readyState\")) return true as unknown as T;\n return undefined as unknown as T;\n }\n\n async screenshot(_options?: ScreenshotOptions): Promise<Buffer> {\n return Buffer.alloc(0);\n }\n async pressKey(_key: string, _options?: WaitOptions): Promise<void> {}\n async type(_text: string, _options?: WaitOptions): Promise<void> {}\n async scroll(_x: number, _y: number, _ref?: ElementRef): Promise<void> {}\n async uploadFile(_ref: ElementRef, _paths: string | readonly string[]): Promise<void> {}\n async download(\n trigger: () => Promise<void>,\n _options?: DownloadOptions\n ): Promise<DownloadResult> {\n await trigger();\n return { path: \"\", suggestedFilename: \"\" };\n }\n onDialog(_handler: (info: DialogInfo) => DialogAction | Promise<DialogAction>): void {}\n\n // -- Tabs -----------------------------------------------------------------\n async tabs(): Promise<readonly TabInfo[]> {\n return this.pages.map((p) => ({ tabId: p.tabId, url: p.url, title: p.title }));\n }\n async switchTab(tabId: string): Promise<void> {\n if (!this.pages.some((p) => p.tabId === tabId)) {\n throw new Error(`ConformanceMockContext: no tab \"${tabId}\"`);\n }\n this.activeTabId = tabId;\n }\n async newTab(url?: string): Promise<TabInfo> {\n const page = this.makePage(url ?? \"about:blank\", \"\");\n this.pages.push(page);\n if (url) {\n const prev = this.activeTabId;\n this.activeTabId = page.tabId;\n await this.navigate(url);\n this.activeTabId = prev;\n }\n return { tabId: page.tabId, url: page.url, title: page.title };\n }\n async closeTab(tabId: string): Promise<void> {\n const before = this.pages.length;\n this.pages = this.pages.filter((p) => p.tabId !== tabId);\n if (this.pages.length === before) {\n throw new Error(`ConformanceMockContext: no tab \"${tabId}\"`);\n }\n if (this.activeTabId === tabId) {\n this.activeTabId = this.pages[this.pages.length - 1]?.tabId;\n }\n }\n\n // -- Wait -----------------------------------------------------------------\n async waitForNavigation(_options?: NavigateOptions): Promise<void> {}\n async waitForSelector(_selector: string, _options?: WaitOptions): Promise<ElementRef> {\n return \"e0\";\n }\n async waitForIdle(_options?: WaitOptions): Promise<void> {}\n}\n",
|
|
6
|
+
"/**\n * @license\n * Copyright 2026 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { BrowserContextFixture } from \"./types\";\n\n// Empty-name buttons are excluded — clickByRole(role, name) requires a\n// non-empty accessible name on every adapter, and adapters treat an empty\n// accessible name as absent. Use \"a\" (single char) as the lower-bound stress.\nconst ARIA_EDGE_CASE_NAMES: ReadonlyArray<string> = [\n \"Sign in\", // baseline ASCII\n \"foo:bar\", // colon mid-name\n \"11:30\", // ends in colon-digits (lastIndexOf parser)\n \"a\", // single character\n \"héllo→\", // unicode + arrow\n \"x\".repeat(200), // long\n];\n\nconst NETWORK_MARKER = \"fixture-network-marker\";\nconst CONSOLE_MARKER = \"fixture-console-marker\";\n\nfunction escapeForHtmlAttr(text: string): string {\n return text\n .replace(/&/g, \"&\")\n .replace(/\"/g, \""\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\");\n}\n\nfunction buildPageHtml(): string {\n const buttons = ARIA_EDGE_CASE_NAMES.map(\n (name, idx) =>\n `<button type=\"button\" aria-label=\"${escapeForHtmlAttr(name)}\" ` +\n `onclick=\"document.getElementById('sentinel').setAttribute('data-clicked', '${idx}')\">` +\n escapeForHtmlAttr(name) +\n `</button>`\n ).join(\"\");\n const head = '<head><meta charset=\"utf-8\"><title>IBrowserContext Conformance</title></head>';\n const body =\n `<body>` +\n `<div id=\"sentinel\" data-clicked=\"\"></div>` +\n buttons +\n `<script>` +\n `console.log(${JSON.stringify(CONSOLE_MARKER)});` +\n // Use a benign network target — data: URL keeps the page self-contained.\n `fetch(\"data:text/plain,${NETWORK_MARKER}\").catch(function(){});` +\n `</script>` +\n `</body>`;\n return `<!DOCTYPE html><html>${head}${body}</html>`;\n}\n\nconst PAGE_URL = `data:text/html,${encodeURIComponent(buildPageHtml())}`;\n\nexport const DEFAULT_FIXTURE: BrowserContextFixture = {\n pageUrl: PAGE_URL,\n networkMarkerUrl: NETWORK_MARKER,\n consoleMarker: CONSOLE_MARKER,\n ariaEdgeCaseNames: ARIA_EDGE_CASE_NAMES,\n};\n\nexport function resolveFixture(\n override: Partial<BrowserContextFixture> | undefined\n): BrowserContextFixture {\n if (!override) return DEFAULT_FIXTURE;\n return { ...DEFAULT_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 { describe } from \"vitest\";\n\nimport { ariaRoundTripBlock } from \"./assertions/ariaRoundTrip\";\nimport { capabilityHonestyBlock } from \"./assertions/capabilityHonesty\";\nimport { networkIntrospectionBlock } from \"./assertions/networkIntrospection\";\nimport { tabsLifecycleBlock } from \"./assertions/tabsLifecycle\";\nimport { resolveFixture } from \"./fixtures\";\nimport type { IBrowserContextConformanceOpts } from \"./types\";\n\nexport function runIBrowserContextConformance(opts: IBrowserContextConformanceOpts): void {\n describe.skipIf(opts.skip)(`IBrowserContext conformance: ${opts.name}`, () => {\n const fixture = resolveFixture(opts.fixture);\n\n // Each block creates its own context via opts.factory() in beforeAll —\n // a fresh handle per block so block N's tab churn / navigation can't\n // taint block N+1. This is intentional even though it means real-browser\n // shims pay one launch cost per block.\n capabilityHonestyBlock(opts, fixture);\n tabsLifecycleBlock(opts, fixture);\n ariaRoundTripBlock(opts, fixture);\n networkIntrospectionBlock(opts, fixture);\n });\n}\n",
|
|
8
|
+
"/**\n * @license\n * Copyright 2026 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { afterAll, beforeAll, describe, expect, it } from \"vitest\";\n\nimport type { AccessibilityNode, IBrowserContext } from \"@workglow/browser-control/task\";\n\nimport type { BrowserContextFixture, IBrowserContextConformanceOpts } from \"../types\";\nimport { itExpectFail } from \"./itExpectFail\";\n\nfunction collectButtons(root: AccessibilityNode): AccessibilityNode[] {\n const out: AccessibilityNode[] = [];\n const stack: AccessibilityNode[] = [root];\n while (stack.length > 0) {\n const n = stack.pop()!;\n if (n.role === \"button\") out.push(n);\n if (n.children) stack.push(...n.children);\n }\n return out;\n}\n\nexport function ariaRoundTripBlock(\n opts: IBrowserContextConformanceOpts,\n fixture: BrowserContextFixture\n): void {\n if (!opts.capabilities.ariaSnapshot) return;\n\n describe(\"ARIA round-trip\", () => {\n const expectFail = (name: string) => opts.expectedFailures?.includes(name) ?? false;\n\n let ctx: IBrowserContext | undefined;\n let handle: Awaited<ReturnType<typeof opts.factory>> | undefined;\n\n beforeAll(async () => {\n handle = await opts.factory();\n ctx = await handle.create();\n // Navigate once; all assertions reuse this page.\n await ctx.navigate(fixture.pageUrl);\n await ctx.waitForIdle({ timeout: 5_000 });\n }, opts.timeout);\n\n afterAll(async () => {\n if (handle && ctx) await handle.dispose(ctx);\n });\n\n // -- Round-trip per edge-case name -------------------------------------\n\n for (let i = 0; i < fixture.ariaEdgeCaseNames.length; i++) {\n const name = fixture.ariaEdgeCaseNames[i];\n const isColonName = name.includes(\":\");\n const title = `clickByRole('button', ${JSON.stringify(name)}) lands on the right node`;\n const body = async () => {\n if (!ctx) throw new Error(\"context not created\");\n const tree = await ctx.snapshot();\n const buttons = collectButtons(tree.root);\n const target = buttons.find((b) => b.name === name);\n expect(\n target,\n `snapshot must include button with name ${JSON.stringify(name)}`\n ).toBeDefined();\n\n await ctx.clickByRole(\"button\", name);\n\n // Read `data-clicked` from the sentinel element.\n const sentinelRef = await ctx.querySelector(\"#sentinel\");\n expect(sentinelRef).not.toBeNull();\n const clicked = await ctx.attribute(sentinelRef!, \"data-clicked\");\n expect(clicked).toBe(String(i));\n };\n // Names containing a colon trigger the lastIndexOf parser bug in\n // PlaywrightBackend. Adapters with that bug list \"aria.colonInName\"\n // in expectedFailures; the helper wraps in `it.fails` for them.\n const useExpectFail = isColonName && expectFail(\"aria.colonInName\");\n if (useExpectFail) {\n itExpectFail(title, body, opts.timeout);\n } else {\n it(title, body, opts.timeout);\n }\n }\n\n // -- Ref reuse after a second snapshot ---------------------------------\n\n it(\n \"refs from snapshot N remain usable for textContent after snapshot N+1\",\n async () => {\n if (!ctx) throw new Error(\"context not created\");\n const t1 = await ctx.snapshot();\n const buttons1 = collectButtons(t1.root);\n expect(buttons1.length).toBeGreaterThan(0);\n const ref = buttons1[0].ref;\n // Take a fresh snapshot, then reach back to the older ref.\n await ctx.snapshot();\n const txt = await ctx.textContent(ref);\n expect(txt, \"ref from prior snapshot should still resolve\").not.toBeNull();\n },\n opts.timeout\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 } from \"vitest\";\n\ntype ItFn = (name: string, fn: () => Promise<void> | void, timeout?: number) => void;\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 {\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",
|
|
10
|
+
"/**\n * @license\n * Copyright 2026 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { afterAll, beforeAll, describe, expect, it } from \"vitest\";\n\nimport type { BrowserContextFixture, IBrowserContextConformanceOpts } from \"../types\";\nimport { itExpectFail } from \"./itExpectFail\";\n\nexport function capabilityHonestyBlock(\n opts: IBrowserContextConformanceOpts,\n fixture: BrowserContextFixture\n): void {\n describe(\"Capability honesty\", () => {\n const expectFail = (name: string) => opts.expectedFailures?.includes(name) ?? false;\n\n let ctx: import(\"@workglow/browser-control/task\").IBrowserContext | undefined;\n let handle: Awaited<ReturnType<typeof opts.factory>> | undefined;\n\n beforeAll(async () => {\n handle = await opts.factory();\n ctx = await handle.create();\n }, opts.timeout);\n\n afterAll(async () => {\n if (handle && ctx) await handle.dispose(ctx);\n });\n\n // -- Negative direction -------------------------------------------------\n\n const runNegative = (\n methodName: \"networkRequests\" | \"consoleMessages\",\n assertionName: string\n ) => {\n const body = async () => {\n if (!ctx) throw new Error(\"context not created\");\n // The contract says: when the capability is false, the method must\n // be strictly undefined — NOT a no-op stub returning [].\n expect(typeof (ctx as unknown as Record<string, unknown>)[methodName]).toBe(\"undefined\");\n };\n const title = `declares ${methodName}=false → ctx.${methodName} is undefined`;\n if (expectFail(assertionName)) {\n itExpectFail(title, body, opts.timeout);\n } else {\n it(title, body, opts.timeout);\n }\n };\n\n if (!opts.capabilities.networkRequests) {\n runNegative(\"networkRequests\", \"capability.networkRequests.undefinedWhenFalse\");\n }\n if (!opts.capabilities.consoleMessages) {\n runNegative(\"consoleMessages\", \"capability.consoleMessages.undefinedWhenFalse\");\n }\n\n // -- Positive direction -------------------------------------------------\n\n if (opts.capabilities.networkRequests) {\n it(\n \"declares networkRequests=true → fixture fetch is observable\",\n async () => {\n if (!ctx) throw new Error(\"context not created\");\n if (typeof ctx.networkRequests !== \"function\") {\n throw new Error(\n \"capability claims networkRequests=true but ctx.networkRequests is not a function\"\n );\n }\n await ctx.navigate(fixture.pageUrl);\n await ctx.waitForIdle({ timeout: 5_000 });\n const entries = await ctx.networkRequests();\n const found = entries.some((r) => r.url.includes(fixture.networkMarkerUrl));\n expect(found, `expected an entry containing ${fixture.networkMarkerUrl}`).toBe(true);\n },\n opts.timeout\n );\n }\n\n if (opts.capabilities.consoleMessages) {\n it(\n \"declares consoleMessages=true → fixture console.log is observable\",\n async () => {\n if (!ctx) throw new Error(\"context not created\");\n if (typeof ctx.consoleMessages !== \"function\") {\n throw new Error(\n \"capability claims consoleMessages=true but ctx.consoleMessages is not a function\"\n );\n }\n await ctx.navigate(fixture.pageUrl);\n await ctx.waitForIdle({ timeout: 5_000 });\n const entries = await ctx.consoleMessages();\n const found = entries.some((m) => m.text.includes(fixture.consoleMarker));\n expect(found, `expected a console message containing ${fixture.consoleMarker}`).toBe(\n true\n );\n },\n opts.timeout\n );\n }\n });\n}\n",
|
|
11
|
+
"/**\n * @license\n * Copyright 2026 Steven Roussey <sroussey@gmail.com>\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { afterAll, beforeAll, describe, expect, it } from \"vitest\";\n\nimport type { IBrowserContext } from \"@workglow/browser-control/task\";\n\nimport type { BrowserContextFixture, IBrowserContextConformanceOpts } from \"../types\";\n\nexport function networkIntrospectionBlock(\n opts: IBrowserContextConformanceOpts,\n fixture: BrowserContextFixture\n): void {\n if (!opts.capabilities.networkRequests && !opts.capabilities.consoleMessages) return;\n\n describe(\"Network/console introspection\", () => {\n let ctx: IBrowserContext | undefined;\n let handle: Awaited<ReturnType<typeof opts.factory>> | undefined;\n\n beforeAll(async () => {\n handle = await opts.factory();\n ctx = await handle.create();\n await ctx.navigate(fixture.pageUrl);\n await ctx.waitForIdle({ timeout: 5_000 });\n }, opts.timeout);\n\n afterAll(async () => {\n if (handle && ctx) await handle.dispose(ctx);\n });\n\n if (opts.capabilities.networkRequests) {\n it(\n \"networkRequests() captures the fixture's outbound fetch\",\n async () => {\n if (!ctx || typeof ctx.networkRequests !== \"function\") {\n throw new Error(\"networkRequests not available despite capability=true\");\n }\n const entries = await ctx.networkRequests();\n const found = entries.some((r) => r.url.includes(fixture.networkMarkerUrl));\n expect(found, `expected an entry containing ${fixture.networkMarkerUrl}`).toBe(true);\n },\n opts.timeout\n );\n\n it(\n \"networkRequests({ method }) filters by method\",\n async () => {\n if (!ctx || typeof ctx.networkRequests !== \"function\") {\n throw new Error(\"networkRequests not available despite capability=true\");\n }\n const all = await ctx.networkRequests();\n const gets = await ctx.networkRequests({ method: \"GET\" });\n // Filter must not return more entries than the unfiltered list.\n expect(gets.length).toBeLessThanOrEqual(all.length);\n },\n opts.timeout\n );\n }\n\n if (opts.capabilities.consoleMessages) {\n it(\n \"consoleMessages() captures the fixture's console.log\",\n async () => {\n if (!ctx || typeof ctx.consoleMessages !== \"function\") {\n throw new Error(\"consoleMessages not available despite capability=true\");\n }\n const entries = await ctx.consoleMessages();\n const found = entries.some((m) => m.text.includes(fixture.consoleMarker));\n expect(found, `expected a console message containing ${fixture.consoleMarker}`).toBe(\n true\n );\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 { afterAll, beforeAll, describe, expect, it } from \"vitest\";\n\nimport type { IBrowserContext } from \"@workglow/browser-control/task\";\n\nimport type { BrowserContextFixture, IBrowserContextConformanceOpts } from \"../types\";\nimport { itExpectFail } from \"./itExpectFail\";\n\nexport function tabsLifecycleBlock(\n opts: IBrowserContextConformanceOpts,\n fixture: BrowserContextFixture\n): void {\n describe(\"Tabs lifecycle\", () => {\n const expectFail = (name: string) => opts.expectedFailures?.includes(name) ?? false;\n\n let ctx: IBrowserContext | undefined;\n let handle: Awaited<ReturnType<typeof opts.factory>> | undefined;\n\n beforeAll(async () => {\n handle = await opts.factory();\n ctx = await handle.create();\n }, opts.timeout);\n\n afterAll(async () => {\n if (handle && ctx) await handle.dispose(ctx);\n });\n\n // -- Basic lifecycle ----------------------------------------------------\n\n it(\n \"tabs() returns at least one tab after connect\",\n async () => {\n if (!ctx) throw new Error(\"context not created\");\n const tabs = await ctx.tabs();\n expect(tabs.length).toBeGreaterThanOrEqual(1);\n },\n opts.timeout\n );\n\n if (opts.capabilities.multipleTabs) {\n it(\n \"newTab() increases tab count by 1\",\n async () => {\n if (!ctx) throw new Error(\"context not created\");\n const before = (await ctx.tabs()).length;\n await ctx.newTab(fixture.pageUrl);\n const after = (await ctx.tabs()).length;\n expect(after).toBe(before + 1);\n },\n opts.timeout\n );\n }\n\n // -- Concurrent close stability ----------------------------------------\n\n if (opts.capabilities.multipleTabs) {\n const title = \"tabId remains valid for surviving tabs across concurrent close()\";\n const body = async () => {\n if (!ctx) throw new Error(\"context not created\");\n // Set up four tabs with distinguishable urls.\n const urls = [\n \"data:text/html,<title>A</title>\",\n \"data:text/html,<title>B</title>\",\n \"data:text/html,<title>C</title>\",\n \"data:text/html,<title>D</title>\",\n ];\n // Reset to a known state by closing existing non-blank tabs.\n for (const t of await ctx.tabs()) {\n // best-effort cleanup; ignore failures\n try {\n await ctx.closeTab(t.tabId);\n } catch {\n /* noop */\n }\n }\n // Some backends require at least one tab; reconnect if needed.\n if ((await ctx.tabs()).length === 0) {\n await ctx.newTab(urls[0]);\n }\n // Ensure exactly four tabs in our known order.\n const baseTabs = await ctx.tabs();\n const opened: string[] = [];\n for (let i = 0; i < urls.length; i++) {\n if (i < baseTabs.length) {\n // navigate the existing one\n await ctx.switchTab(baseTabs[i].tabId);\n await ctx.navigate(urls[i]);\n opened.push(baseTabs[i].tabId);\n } else {\n const t = await ctx.newTab(urls[i]);\n opened.push(t.tabId);\n }\n }\n const [aId, bId, cId, dId] = opened;\n // Concurrent close.\n await Promise.all([ctx.closeTab(aId), ctx.closeTab(bId)]);\n // Surviving tabs C and D should still resolve to their original urls.\n await ctx.switchTab(cId);\n expect(await ctx.currentUrl()).toBe(urls[2]);\n await ctx.switchTab(dId);\n expect(await ctx.currentUrl()).toBe(urls[3]);\n };\n if (expectFail(\"tabs.concurrentCloseStable\")) {\n itExpectFail(title, body, opts.timeout);\n } else {\n it(title, body, opts.timeout);\n }\n } else {\n // Single-view backends: closeTab(only tabId) must either disconnect\n // OR leave a single tab — accept either result, just don't crash.\n it(\n \"single-view: closeTab on the sole tab disconnects or no-ops\",\n async () => {\n if (!ctx) throw new Error(\"context not created\");\n const [t] = await ctx.tabs();\n try {\n await ctx.closeTab(t.tabId);\n } catch {\n /* some single-view backends may throw — that is acceptable */\n }\n // Either disconnected or still has at most one tab.\n if (ctx.isConnected()) {\n const after = await ctx.tabs();\n expect(after.length).toBeLessThanOrEqual(1);\n }\n },\n opts.timeout\n );\n }\n });\n}\n"
|
|
13
|
+
],
|
|
14
|
+
"mappings": "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",
|
|
15
|
+
"debugId": "CFA08057753935AD64756E2164756E21",
|
|
16
|
+
"names": []
|
|
17
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Steven Roussey <sroussey@gmail.com>
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { it as vitestIt } from "vitest";
|
|
7
|
+
export declare function isCreditExhaustedError(err: unknown): boolean;
|
|
8
|
+
export declare function creditSkipNote(err: unknown): string;
|
|
9
|
+
/**
|
|
10
|
+
* Whether a billing failure skips the test or fails it.
|
|
11
|
+
*
|
|
12
|
+
* CI skips: nobody watching a build can top an account up, and one exhausted
|
|
13
|
+
* key would otherwise turn every provider suite red for a change that touched
|
|
14
|
+
* none of them — so the leniency is provider-agnostic by construction, keyed on
|
|
15
|
+
* the environment rather than on which vendor ran out.
|
|
16
|
+
*
|
|
17
|
+
* Locally it fails, loudly, because the developer running the suite IS the
|
|
18
|
+
* person who can act on it — and a silently green local run is how an
|
|
19
|
+
* exhausted account goes unnoticed until CI is the only thing exercising the
|
|
20
|
+
* provider at all.
|
|
21
|
+
*
|
|
22
|
+
* `WORKGLOW_CREDIT_EXHAUSTED_SKIP` overrides in both directions.
|
|
23
|
+
*/
|
|
24
|
+
export declare function creditExhaustionSkipsTest(env?: Record<string, string | undefined>): boolean;
|
|
25
|
+
export declare function runWithCreditSkip<T>(ctx: {
|
|
26
|
+
skip: (note?: string) => void;
|
|
27
|
+
}, fn: () => Promise<T> | T): Promise<T>;
|
|
28
|
+
/**
|
|
29
|
+
* Wraps one test body. This is the seam every conformance assertion actually
|
|
30
|
+
* runs through (the exported {@link it} proxies each callback through it), so
|
|
31
|
+
* the CI-skips / locally-fails policy is applied here rather than at each
|
|
32
|
+
* call site.
|
|
33
|
+
*/
|
|
34
|
+
export declare function wrapTestBodyForCreditSkip(fn: (...args: never[]) => unknown): (...args: never[]) => unknown;
|
|
35
|
+
/**
|
|
36
|
+
* Drop-in `it` for live provider tests: a credit/billing error skips the test on
|
|
37
|
+
* CI and fails it locally (see {@link creditExhaustionSkipsTest}).
|
|
38
|
+
*/
|
|
39
|
+
export declare const it: typeof vitestIt;
|
|
@@ -0,0 +1,163 @@
|
|
|
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
|
+
export {
|
|
155
|
+
creditExhaustionSkipsTest,
|
|
156
|
+
creditSkipNote,
|
|
157
|
+
isCreditExhaustedError,
|
|
158
|
+
it,
|
|
159
|
+
runWithCreditSkip,
|
|
160
|
+
wrapTestBodyForCreditSkip
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
//# debugId=0B9CD907030C33DF64756E2164756E21
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/creditExhaustedSkip.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
|
+
],
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "0B9CD907030C33DF64756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Steven Roussey <sroussey@gmail.com>
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import type { EntitlementProfileConformanceHandle, EntitlementProfileConformanceOpts } from "../types";
|
|
7
|
+
/**
|
|
8
|
+
* The denial returned by requestEntitlement for an uncovered entitlement
|
|
9
|
+
* must satisfy the EntitlementDenial discriminated union: a `reason` of
|
|
10
|
+
* "policy-deny" / "user-deny" requires `matchedRule`; "default-deny"
|
|
11
|
+
* forbids it.
|
|
12
|
+
*/
|
|
13
|
+
export declare function denialShapeBlock(_opts: EntitlementProfileConformanceOpts, getHandle: () => EntitlementProfileConformanceHandle): void;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Steven Roussey <sroussey@gmail.com>
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import type { EntitlementProfileConformanceHandle, EntitlementProfileConformanceOpts } from "../types";
|
|
7
|
+
/**
|
|
8
|
+
* dispose() is idempotent. After dispose, a simulateSignal does not deliver
|
|
9
|
+
* events to listeners that subscribed before dispose.
|
|
10
|
+
*
|
|
11
|
+
* NOTE: This block uses an isolated profile via the factory, not the shared
|
|
12
|
+
* conformance handle, because disposing the shared handle would break later
|
|
13
|
+
* blocks. The factory is invoked again here; afterAll on the shared handle
|
|
14
|
+
* still runs against its own profile.
|
|
15
|
+
*/
|
|
16
|
+
export declare function disposeBlock(opts: EntitlementProfileConformanceOpts, _getHandle: () => EntitlementProfileConformanceHandle): void;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Steven Roussey <sroussey@gmail.com>
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import type { EntitlementProfileConformanceHandle, EntitlementProfileConformanceOpts } from "../types";
|
|
7
|
+
/**
|
|
8
|
+
* If the profile surface includes a parent entitlement (e.g. "network"),
|
|
9
|
+
* requesting a child entitlement (e.g. "network:http") must be granted.
|
|
10
|
+
*/
|
|
11
|
+
export declare function hierarchyHonoringBlock(opts: EntitlementProfileConformanceOpts, getHandle: () => EntitlementProfileConformanceHandle): void;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Steven Roussey <sroussey@gmail.com>
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import type { EntitlementProfileConformanceHandle, EntitlementProfileConformanceOpts } from "../types";
|
|
7
|
+
/**
|
|
8
|
+
* Optional entitlements must never be reported as denied — neither by
|
|
9
|
+
* `requestEntitlement` nor by `checkAll`.
|
|
10
|
+
*/
|
|
11
|
+
export declare function optionalNeverDeniedBlock(_opts: EntitlementProfileConformanceOpts, getHandle: () => EntitlementProfileConformanceHandle): void;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Steven Roussey <sroussey@gmail.com>
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import type { EntitlementProfileConformanceHandle, EntitlementProfileConformanceOpts } from "../types";
|
|
7
|
+
/**
|
|
8
|
+
* For the same input, `requestEntitlement` and `checkAll([input])` agree:
|
|
9
|
+
* - granted ↔ empty denial array
|
|
10
|
+
* - denied ↔ single-element denial array with matching reason
|
|
11
|
+
*/
|
|
12
|
+
export declare function requestEntitlementShapeBlock(_opts: EntitlementProfileConformanceOpts, getHandle: () => EntitlementProfileConformanceHandle): void;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Steven Roussey <sroussey@gmail.com>
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import type { EntitlementProfileConformanceHandle, EntitlementProfileConformanceOpts } from "../types";
|
|
7
|
+
/**
|
|
8
|
+
* For a profile that supports resource scoping, build a temporary scoped
|
|
9
|
+
* grant by augmenting the profile is out of scope here — instead we
|
|
10
|
+
* exercise the policy with a known-scoped fixture only when the profile
|
|
11
|
+
* already grants filesystem broadly. When `filesystem` is broadly granted,
|
|
12
|
+
* a scoped read of any resource succeeds. This block additionally verifies
|
|
13
|
+
* that profiles which DO NOT grant filesystem deny a scoped read of
|
|
14
|
+
* `/etc/passwd`.
|
|
15
|
+
*/
|
|
16
|
+
export declare function resourceScopingBlock(opts: EntitlementProfileConformanceOpts, getHandle: () => EntitlementProfileConformanceHandle): void;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Steven Roussey <sroussey@gmail.com>
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import type { EntitlementProfileConformanceHandle, EntitlementProfileConformanceOpts } from "../types";
|
|
7
|
+
/**
|
|
8
|
+
* Verify that the profile emits a "granted" change event when the signal
|
|
9
|
+
* source emits a grant for a previously-denied entitlement.
|
|
10
|
+
*/
|
|
11
|
+
export declare function subscribeGrantBlock(opts: EntitlementProfileConformanceOpts, getHandle: () => EntitlementProfileConformanceHandle): void;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Steven Roussey <sroussey@gmail.com>
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import type { EntitlementProfileConformanceHandle, EntitlementProfileConformanceOpts } from "../types";
|
|
7
|
+
/**
|
|
8
|
+
* After querying a set of entitlements then signalling reload, change events
|
|
9
|
+
* fire only for entitlements whose verdict actually flipped.
|
|
10
|
+
*/
|
|
11
|
+
export declare function subscribeReloadBlock(opts: EntitlementProfileConformanceOpts, getHandle: () => EntitlementProfileConformanceHandle): void;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Steven Roussey <sroussey@gmail.com>
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import type { EntitlementProfileConformanceHandle, EntitlementProfileConformanceOpts } from "../types";
|
|
7
|
+
/**
|
|
8
|
+
* Verify that the profile re-evaluates and emits a "revoked" change event
|
|
9
|
+
* when the signal source emits a revoke for a previously-granted
|
|
10
|
+
* entitlement, and emits nothing when no flip occurred.
|
|
11
|
+
*/
|
|
12
|
+
export declare function subscribeRevocationBlock(opts: EntitlementProfileConformanceOpts, getHandle: () => EntitlementProfileConformanceHandle): 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 { EntitlementProfileConformanceHandle, EntitlementProfileConformanceOpts } from "../types";
|
|
7
|
+
export declare function surfaceCoverageBlock(opts: EntitlementProfileConformanceOpts, getHandle: () => EntitlementProfileConformanceHandle): 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 { EntitlementProfileConformanceHandle, EntitlementProfileConformanceOpts } from "../types";
|
|
7
|
+
export declare function unsubscribeIdempotentBlock(opts: EntitlementProfileConformanceOpts, getHandle: () => EntitlementProfileConformanceHandle): void;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Steven Roussey <sroussey@gmail.com>
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { type EntitlementSignal, type IEntitlementSignalSource, type TaskEntitlement } from "@workglow/task-graph";
|
|
7
|
+
export declare const NETWORK_HTTP_REQUIRED: TaskEntitlement;
|
|
8
|
+
export declare const FILESYSTEM_REQUIRED: TaskEntitlement;
|
|
9
|
+
export declare const OPTIONAL_CREDENTIAL: TaskEntitlement;
|
|
10
|
+
export declare const SCOPED_FILESYSTEM_TMP_OK: TaskEntitlement;
|
|
11
|
+
export declare const SCOPED_FILESYSTEM_ETC_BAD: TaskEntitlement;
|
|
12
|
+
/** Guaranteed not to appear in any built-in profile surface. */
|
|
13
|
+
export declare const UNCOVERED_FOO: TaskEntitlement;
|
|
14
|
+
/**
|
|
15
|
+
* In-memory signal source for the Custom_Profile shim. The returned source
|
|
16
|
+
* exposes `emit` so the shim's `simulateSignal` can drive listeners.
|
|
17
|
+
*/
|
|
18
|
+
export interface ControllableSignalSource extends IEntitlementSignalSource {
|
|
19
|
+
emit(signal: EntitlementSignal): void;
|
|
20
|
+
}
|
|
21
|
+
export declare function createControllableSignalSource(): ControllableSignalSource;
|
|
@@ -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 { EntitlementProfileConformanceOpts } from "./types";
|
|
7
|
+
export declare function runEntitlementProfileConformance(opts: EntitlementProfileConformanceOpts): void;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Steven Roussey <sroussey@gmail.com>
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import type { EntitlementId, EntitlementSignal, IEntitlementProfile } from "@workglow/task-graph";
|
|
7
|
+
export interface EntitlementProfileConformanceOpts {
|
|
8
|
+
readonly name: string;
|
|
9
|
+
readonly skip?: boolean;
|
|
10
|
+
readonly timeout: number;
|
|
11
|
+
readonly factory: () => Promise<EntitlementProfileConformanceHandle>;
|
|
12
|
+
readonly capabilities: EntitlementProfileCapabilities;
|
|
13
|
+
readonly expected: EntitlementProfileExpected;
|
|
14
|
+
}
|
|
15
|
+
export interface EntitlementProfileCapabilities {
|
|
16
|
+
/**
|
|
17
|
+
* If true, the factory returns a handle whose `simulateSignal` method
|
|
18
|
+
* is wired to the profile's signal source. Subscribe/* assertions are
|
|
19
|
+
* gated on this flag.
|
|
20
|
+
*/
|
|
21
|
+
readonly mutableSignalSource: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* If true, hierarchyHonoringBlock runs. All built-ins set this true
|
|
24
|
+
* because they use `createPolicyEnforcer` which honors hierarchy.
|
|
25
|
+
*/
|
|
26
|
+
readonly hierarchyHonoring: boolean;
|
|
27
|
+
/** If true, resourceScopingBlock runs. */
|
|
28
|
+
readonly resourceScoping: boolean;
|
|
29
|
+
}
|
|
30
|
+
export interface EntitlementProfileExpected {
|
|
31
|
+
/** Entitlement IDs that MUST appear in the profile's surface(). */
|
|
32
|
+
readonly surfaceIncludes: readonly EntitlementId[];
|
|
33
|
+
/** Entitlement IDs that MUST NOT appear in the profile's surface(). */
|
|
34
|
+
readonly surfaceExcludes: readonly EntitlementId[];
|
|
35
|
+
}
|
|
36
|
+
export interface EntitlementProfileConformanceHandle {
|
|
37
|
+
readonly profile: IEntitlementProfile;
|
|
38
|
+
/**
|
|
39
|
+
* Present iff `capabilities.mutableSignalSource` is true. Pushes a signal
|
|
40
|
+
* as if the underlying source emitted it, so subscribe/* assertions can
|
|
41
|
+
* exercise the verdict-flip path.
|
|
42
|
+
*/
|
|
43
|
+
simulateSignal?(signal: EntitlementSignal): void;
|
|
44
|
+
dispose(): Promise<void>;
|
|
45
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Steven Roussey <sroussey@gmail.com>
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Conformance suite for `IEntitlementProfile` — denial shape, hierarchy and
|
|
8
|
+
* subscription behaviour, over every shipped profile.
|
|
9
|
+
*/
|
|
10
|
+
export * from "./entitlement-profile/fixtures";
|
|
11
|
+
export * from "./entitlement-profile/runEntitlementProfileConformance";
|
|
12
|
+
export * from "./entitlement-profile/types";
|
|
13
|
+
export * from "./entitlement-profile/assertions/denialShape";
|
|
14
|
+
export * from "./entitlement-profile/assertions/dispose";
|
|
15
|
+
export * from "./entitlement-profile/assertions/hierarchyHonoring";
|
|
16
|
+
export * from "./entitlement-profile/assertions/optionalNeverDenied";
|
|
17
|
+
export * from "./entitlement-profile/assertions/requestEntitlementShape";
|
|
18
|
+
export * from "./entitlement-profile/assertions/resourceScoping";
|
|
19
|
+
export * from "./entitlement-profile/assertions/subscribeGrant";
|
|
20
|
+
export * from "./entitlement-profile/assertions/subscribeReload";
|
|
21
|
+
export * from "./entitlement-profile/assertions/subscribeRevocation";
|
|
22
|
+
export * from "./entitlement-profile/assertions/surfaceCoverage";
|
|
23
|
+
export * from "./entitlement-profile/assertions/unsubscribeIdempotent";
|