@skyramp/mcp 0.3.2 → 0.3.4

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.
Files changed (181) hide show
  1. package/build/index.js +47 -2
  2. package/build/playwright/PlaywrightTraceService.d.ts +8 -0
  3. package/build/playwright/PlaywrightTraceService.js +1 -0
  4. package/build/playwright/registerPlaywrightTools.js +42 -1
  5. package/build/prompts/enhance-assertions/sharedAssertionRules.js +19 -0
  6. package/build/prompts/pom-aware-code-reuse.js +17 -8
  7. package/build/prompts/test-maintenance/actionsInstructions.js +2 -2
  8. package/build/prompts/test-recommendation/analysisOutputPrompt.js +1 -4
  9. package/build/prompts/test-recommendation/recommendationSections.d.ts +1 -1
  10. package/build/prompts/test-recommendation/recommendationSections.js +5 -5
  11. package/build/prompts/test-recommendation/test-recommendation-prompt.js +13 -7
  12. package/build/prompts/testbot/testbot-prompts.d.ts +2 -12
  13. package/build/prompts/testbot/testbot-prompts.js +28 -16
  14. package/build/recommendation/discriminators.d.ts +7 -1
  15. package/build/recommendation/discriminators.js +16 -3
  16. package/build/resources/testbotResource.js +20 -4
  17. package/build/services/ScenarioGenerationService.js +5 -2
  18. package/build/services/TestExecutionService.d.ts +13 -8
  19. package/build/services/TestExecutionService.js +73 -26
  20. package/build/services/TestGenerationService.js +24 -9
  21. package/build/services/containerEnv.d.ts +12 -1
  22. package/build/services/containerEnv.js +118 -1
  23. package/build/tools/executeSkyrampTestTool.d.ts +9 -0
  24. package/build/tools/executeSkyrampTestTool.js +20 -6
  25. package/build/tools/execution-video-state.d.ts +21 -0
  26. package/build/tools/execution-video-state.js +51 -0
  27. package/build/tools/generate-tests/generateBatchScenarioRestTool.js +31 -11
  28. package/build/tools/generate-tests/planGuard.d.ts +5 -5
  29. package/build/tools/generate-tests/planGuard.js +5 -17
  30. package/build/tools/queryProxyMocksTool.js +0 -1
  31. package/build/tools/submitReportTool.d.ts +83 -10
  32. package/build/tools/submitReportTool.js +179 -29
  33. package/build/tools/test-management/actionsTool.js +52 -41
  34. package/build/tools/test-management/analyzeChangesTool.d.ts +11 -0
  35. package/build/tools/test-management/analyzeChangesTool.js +37 -33
  36. package/build/tools/test-management/analyzeTestHealthTool.js +3 -3
  37. package/build/tools/test-management/registerTestPlanTool.js +113 -31
  38. package/build/types/TestAnalysis.d.ts +7 -3
  39. package/build/types/TestExecution.d.ts +14 -0
  40. package/build/types/TestTypes.js +3 -2
  41. package/build/types/TestbotPromptOptions.d.ts +34 -0
  42. package/build/types/TestbotPromptOptions.js +1 -0
  43. package/build/types/TestbotReport.d.ts +10 -0
  44. package/build/types/TestbotReport.js +10 -1
  45. package/build/types/index.d.ts +2 -0
  46. package/build/types/index.js +1 -0
  47. package/build/utils/AnalysisStateManager.d.ts +36 -2
  48. package/build/utils/AnalysisStateManager.js +34 -13
  49. package/build/utils/frontendSelectors.js +0 -1
  50. package/build/utils/gitStaging.d.ts +5 -0
  51. package/build/utils/gitStaging.js +1 -1
  52. package/build/utils/pom-catalog.d.ts +23 -0
  53. package/build/utils/pom-catalog.js +30 -0
  54. package/build/utils/pom-scope/pom-files.d.ts +14 -0
  55. package/build/utils/pom-scope/pom-files.js +32 -6
  56. package/build/utils/pom-scope/testIdDiscovery.d.ts +40 -0
  57. package/build/utils/pom-scope/testIdDiscovery.js +104 -0
  58. package/build/utils/reportVerification.d.ts +7 -2
  59. package/build/utils/reportVerification.js +9 -3
  60. package/build/utils/scenarioDrafting.js +7 -1
  61. package/build/utils/skyrampMdContent.d.ts +1 -1
  62. package/build/utils/skyrampMdContent.js +1 -1
  63. package/build/utils/urlPath.d.ts +37 -0
  64. package/build/utils/urlPath.js +55 -0
  65. package/build/utils/utils.d.ts +45 -0
  66. package/build/utils/utils.js +50 -0
  67. package/build/utils/versions.d.ts +3 -3
  68. package/build/utils/versions.js +1 -1
  69. package/build/utils/workspaceAuth.d.ts +15 -15
  70. package/build/utils/workspaceAuth.js +32 -17
  71. package/build/workspace/queryParamResolution.d.ts +93 -0
  72. package/build/workspace/queryParamResolution.js +201 -0
  73. package/build/workspace/workspace.d.ts +104 -0
  74. package/build/workspace/workspace.js +24 -0
  75. package/node_modules/playwright/ThirdPartyNotices.txt +319 -266
  76. package/node_modules/playwright/lib/dom-analyzer/blueprint.js +154 -27
  77. package/node_modules/playwright/lib/dom-analyzer/crawler.js +2 -2
  78. package/node_modules/playwright/lib/mcp/browser/tools/pageBlueprint.js +1 -1
  79. package/node_modules/playwright/lib/mcp/browser/tools/sitemap.js +6 -2
  80. package/node_modules/playwright/lib/mcp/skyramp/loadTraceTool.js +3 -2
  81. package/node_modules/playwright/lib/mcp/skyramp/resultCode.js +4 -3
  82. package/node_modules/playwright/lib/mcp/skyramp/skyRampImport.js +15 -3
  83. package/node_modules/playwright/lib/mcp/skyramp/specImport.js +781 -0
  84. package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +14 -3
  85. package/node_modules/playwright/lib/mcp/test/resultCode.test.js +2 -1
  86. package/node_modules/playwright/lib/mcp/test/skyRampExport.js +1 -1
  87. package/node_modules/playwright/lib/mcp/test/skyRampExport.test.js +30 -0
  88. package/node_modules/playwright/lib/transform/babelBundleImpl.js +200 -199
  89. package/node_modules/playwright/node_modules/playwright-core/ThirdPartyNotices.txt +3 -3
  90. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/.package-lock.json +3 -3
  91. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/adapter/aws-lambda/handler.js +16 -25
  92. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/adapter/bun/websocket.js +3 -1
  93. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/adapter/lambda-edge/handler.js +20 -4
  94. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/adapter/aws-lambda/handler.js +16 -25
  95. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/adapter/bun/websocket.js +3 -1
  96. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/adapter/lambda-edge/handler.js +20 -4
  97. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/client/client.js +10 -1
  98. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/client/utils.js +1 -1
  99. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/helper/css/common.js +3 -1
  100. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/helper/css/index.js +9 -1
  101. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/jsx/base.js +8 -14
  102. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/jsx/components.js +41 -21
  103. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/jsx/context.js +131 -5
  104. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/jsx/streaming.js +9 -7
  105. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/bearer-auth/index.js +1 -1
  106. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/cache/index.js +1 -1
  107. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/compress/index.js +2 -1
  108. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/cors/index.js +2 -5
  109. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/etag/index.js +2 -1
  110. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/language/language.js +10 -32
  111. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/method-override/index.js +5 -3
  112. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/serve-static/index.js +2 -2
  113. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/timing/timing.js +3 -1
  114. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/router/trie-router/node.js +9 -0
  115. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/utils/body.js +12 -4
  116. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/utils/buffer.js +2 -1
  117. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/utils/ipaddr.js +6 -1
  118. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/validator/validator.js +3 -3
  119. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/client/client.js +10 -1
  120. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/client/utils.js +1 -1
  121. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/helper/css/common.js +3 -1
  122. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/helper/css/index.js +9 -1
  123. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/jsx/base.js +15 -15
  124. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/jsx/components.js +42 -22
  125. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/jsx/context.js +129 -5
  126. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/jsx/streaming.js +10 -8
  127. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/bearer-auth/index.js +1 -1
  128. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/cache/index.js +1 -1
  129. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/compress/index.js +2 -1
  130. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/cors/index.js +2 -5
  131. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/etag/index.js +2 -1
  132. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/language/language.js +10 -32
  133. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/method-override/index.js +5 -3
  134. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/serve-static/index.js +2 -2
  135. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/timing/timing.js +3 -1
  136. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/router/trie-router/node.js +9 -0
  137. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/adapter/aws-lambda/handler.d.ts +1 -1
  138. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/adapter/lambda-edge/handler.d.ts +1 -1
  139. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/helper/websocket/index.d.ts +1 -1
  140. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/jsx/base.d.ts +1 -3
  141. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/jsx/context.d.ts +39 -0
  142. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/middleware/context-storage/index.d.ts +2 -2
  143. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/middleware/language/language.d.ts +18 -0
  144. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/utils/body.d.ts +1 -1
  145. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/utils/types.d.ts +1 -1
  146. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/utils/body.js +12 -4
  147. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/utils/buffer.js +2 -1
  148. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/utils/ipaddr.js +6 -1
  149. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/validator/validator.js +3 -3
  150. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/package.json +29 -22
  151. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/package-lock.json +3 -3
  152. package/node_modules/playwright/node_modules/playwright-core/lib/generated/pollingRecorderSource.js +1 -1
  153. package/node_modules/playwright/node_modules/playwright-core/lib/server/codegen/skyramp/jsonlReader.js +5 -1
  154. package/node_modules/playwright/node_modules/playwright-core/lib/server/codegen/skyramp/specReader.js +781 -0
  155. package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/recorderApp.js +25 -6
  156. package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/skyramp/replayEngine.js +3 -1
  157. package/node_modules/playwright/node_modules/playwright-core/lib/utils.js +2 -0
  158. package/node_modules/playwright/node_modules/playwright-core/lib/vite/htmlReport/index.html +253 -27
  159. package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/{codeMirrorModule-D0BjbCb7.js → codeMirrorModule-DtudTj_v.js} +1 -1
  160. package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/index-BpDwp16L.js +422 -0
  161. package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/index.html +1 -1
  162. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{codeMirrorModule-Bzd72-bG.js → codeMirrorModule-FNMuBzX1.js} +1 -1
  163. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-Co9upU5h.js +1035 -0
  164. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.DXNIQ_dx.js +2 -0
  165. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +2 -2
  166. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.CIKB3XSv.js +5 -0
  167. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +2 -2
  168. package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
  169. package/node_modules/playwright/node_modules/playwright-core/src/generated/pollingRecorderSource.ts +1 -1
  170. package/node_modules/playwright/node_modules/playwright-core/src/server/codegen/skyramp/jsonlReader.ts +4 -0
  171. package/node_modules/playwright/node_modules/playwright-core/src/server/codegen/skyramp/specReader.ts +1028 -0
  172. package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/recorderApp.ts +31 -8
  173. package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/skyramp/replayEngine.ts +1 -0
  174. package/node_modules/playwright/node_modules/playwright-core/src/utils.ts +1 -0
  175. package/node_modules/playwright/package.json +1 -1
  176. package/package.json +10 -6
  177. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/tsconfig.build.tsbuildinfo +0 -1
  178. package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/index-lvTRGFx-.js +0 -193
  179. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-DzxTioTK.js +0 -809
  180. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.BGc30U3S.js +0 -2
  181. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.IaDrb29A.js +0 -5
@@ -1,3 +1,4 @@
1
+ import { type ScopedQueryParams } from "../workspace/queryParamResolution.js";
1
2
  /**
2
3
  * Reads `.skyramp/workspace.yml`.
3
4
  *
@@ -133,20 +134,6 @@ export declare function getWorkspaceAuthConfig(repositoryPath: string): Promise<
133
134
  /** True when any workspace service declares api.skipTLSVerify — the SUT serves
134
135
  * self-signed TLS and generated tests must skip cert verification (SKYR-3961). */
135
136
  export declare function getWorkspaceSkipTLSVerify(repositoryPath: string): Promise<boolean>;
136
- /**
137
- * Resolve api.defaultQueryParams for test generation — query params always
138
- * attached to every generated request for this service (e.g. RBAC context
139
- * an authorization layer requires beyond the auth header). Same
140
- * first-matching-service convention as getWorkspaceAuthConfig (SKYR-4050).
141
- *
142
- * Unlike readWorkspaceConfigRaw (which delegates to WorkspaceConfigManager and
143
- * resolves EXACTLY `<repositoryPath>/.skyramp/workspace.yml`), this walks up
144
- * from `repositoryPath` to find the nearest `.skyramp/workspace.yml` — both
145
- * call sites pass a nested test-output directory (e.g. `<repo>/tests/skyramp`),
146
- * not the repo root, so an exact-match lookup would silently never find the
147
- * config. Mirrors the sync fs.existsSync walk-up in
148
- * generateBatchScenarioRestTool.ts.
149
- */
150
137
  /**
151
138
  * Walk up from `startDir` to find the nearest `.skyramp/workspace.yml`, checking
152
139
  * `startDir` itself first and continuing through every ancestor INCLUDING the
@@ -156,7 +143,20 @@ export declare function getWorkspaceSkipTLSVerify(repositoryPath: string): Promi
156
143
  * `existsSync` is injectable for testing; defaults to the real filesystem.
157
144
  */
158
145
  export declare function findWorkspaceConfigPath(startDir: string, existsSync?: (p: string) => boolean): string | null;
159
- export declare function getWorkspaceDefaultQueryParams(repositoryPath: string): Promise<Record<string, string> | undefined>;
146
+ /**
147
+ * Resolve api.defaultQueryParams together with api.queryParamOverrides for test
148
+ * generation (SKYR-4127). Same first-matching-service convention as
149
+ * getWorkspaceAuthConfig: the first service declaring EITHER key wins.
150
+ *
151
+ * Reads the YAML directly rather than through the Zod schema — the walk-up must
152
+ * stay cheap, and hand-rolled fixtures routinely omit fields the strict schema
153
+ * requires. normalizeOverrides therefore validates the override list itself,
154
+ * once per load rather than per request.
155
+ *
156
+ * Returns undefined when no config is found or no service declares either key,
157
+ * so callers can skip the merge entirely.
158
+ */
159
+ export declare function getWorkspaceScopedQueryParams(repositoryPath: string): Promise<ScopedQueryParams | undefined>;
160
160
  /**
161
161
  * Merge workspace-declared default query params into a comma-separated
162
162
  * "key=value,key2=value2" queryParams string (the format used by
@@ -2,6 +2,7 @@ import * as fs from "fs";
2
2
  import path from "path";
3
3
  import yaml from "js-yaml";
4
4
  import { WorkspaceConfigManager } from "../workspace/workspace.js";
5
+ import { normalizeBaseMap, normalizeOverrides } from "../workspace/queryParamResolution.js";
5
6
  import { logger } from "./logger.js";
6
7
  /**
7
8
  * Reads `.skyramp/workspace.yml`.
@@ -363,20 +364,6 @@ export async function getWorkspaceSkipTLSVerify(repositoryPath) {
363
364
  return false;
364
365
  return (rawConfig.services ?? []).some((s) => s.api?.skipTLSVerify === true);
365
366
  }
366
- /**
367
- * Resolve api.defaultQueryParams for test generation — query params always
368
- * attached to every generated request for this service (e.g. RBAC context
369
- * an authorization layer requires beyond the auth header). Same
370
- * first-matching-service convention as getWorkspaceAuthConfig (SKYR-4050).
371
- *
372
- * Unlike readWorkspaceConfigRaw (which delegates to WorkspaceConfigManager and
373
- * resolves EXACTLY `<repositoryPath>/.skyramp/workspace.yml`), this walks up
374
- * from `repositoryPath` to find the nearest `.skyramp/workspace.yml` — both
375
- * call sites pass a nested test-output directory (e.g. `<repo>/tests/skyramp`),
376
- * not the repo root, so an exact-match lookup would silently never find the
377
- * config. Mirrors the sync fs.existsSync walk-up in
378
- * generateBatchScenarioRestTool.ts.
379
- */
380
367
  /**
381
368
  * Walk up from `startDir` to find the nearest `.skyramp/workspace.yml`, checking
382
369
  * `startDir` itself first and continuing through every ancestor INCLUDING the
@@ -398,15 +385,43 @@ export function findWorkspaceConfigPath(startDir, existsSync = fs.existsSync) {
398
385
  searchDir = parentDir;
399
386
  }
400
387
  }
401
- export async function getWorkspaceDefaultQueryParams(repositoryPath) {
388
+ /**
389
+ * Resolve api.defaultQueryParams together with api.queryParamOverrides for test
390
+ * generation (SKYR-4127). Same first-matching-service convention as
391
+ * getWorkspaceAuthConfig: the first service declaring EITHER key wins.
392
+ *
393
+ * Reads the YAML directly rather than through the Zod schema — the walk-up must
394
+ * stay cheap, and hand-rolled fixtures routinely omit fields the strict schema
395
+ * requires. normalizeOverrides therefore validates the override list itself,
396
+ * once per load rather than per request.
397
+ *
398
+ * Returns undefined when no config is found or no service declares either key,
399
+ * so callers can skip the merge entirely.
400
+ */
401
+ export async function getWorkspaceScopedQueryParams(repositoryPath) {
402
402
  const wsConfigPath = findWorkspaceConfigPath(repositoryPath);
403
403
  if (!wsConfigPath)
404
404
  return undefined;
405
405
  try {
406
406
  const raw = fs.readFileSync(wsConfigPath, "utf-8");
407
407
  const parsed = yaml.load(raw);
408
- const svc = parsed?.services?.find((s) => s.api?.defaultQueryParams !== undefined);
409
- return svc?.api?.defaultQueryParams;
408
+ const declaring = (parsed?.services ?? []).filter((s) => s?.api?.defaultQueryParams !== undefined || s?.api?.queryParamOverrides !== undefined);
409
+ const svc = declaring[0];
410
+ if (!svc)
411
+ return undefined;
412
+ // First-wins is deliberate (same convention as getWorkspaceAuthConfig) —
413
+ // but silently discarding a later service's config is easy to miss, so
414
+ // name what's being ignored (Finding 3, SKYR-4127 final review).
415
+ if (declaring.length > 1) {
416
+ logger.warning("Multiple services declare defaultQueryParams/queryParamOverrides — using the first, ignoring the rest", {
417
+ used: svc.serviceName,
418
+ ignored: declaring.slice(1).map((s) => s.serviceName),
419
+ });
420
+ }
421
+ return {
422
+ base: normalizeBaseMap(svc.api.defaultQueryParams),
423
+ overrides: normalizeOverrides(svc.api.queryParamOverrides),
424
+ };
410
425
  }
411
426
  catch {
412
427
  return undefined;
@@ -0,0 +1,93 @@
1
+ export interface QueryParamOverride {
2
+ /** picomatch glob matched against the request URL pathname. OpenAPI-style
3
+ * `{param}` placeholders are treated as a single-segment wildcard. */
4
+ pathPattern: string;
5
+ /**
6
+ * Known wart (Finding 6, SKYR-4127 final review): `values: {}` is a valid,
7
+ * deliberately-typed empty map. If its pathPattern is the single most
8
+ * specific match for a request, it wins and contributes nothing — silently
9
+ * suppressing a broader override that would otherwise have applied. Not
10
+ * validated against because an intentionally no-op override (e.g. "no
11
+ * extra params for this path, unlike its siblings") is a legitimate use.
12
+ */
13
+ values: Record<string, string>;
14
+ }
15
+ export interface ScopedQueryParams {
16
+ /** api.defaultQueryParams — applied to every request for the service. */
17
+ base: Record<string, string>;
18
+ /** api.queryParamOverrides — at most one applies to any given request. */
19
+ overrides: QueryParamOverride[];
20
+ }
21
+ /** Pathname of a full endpoint URL, or undefined if unparseable.
22
+ * `endpointURL` is contractually base URL + path, so the result includes any
23
+ * base-path prefix: `https://host/api/v1/persons/42` -> `/api/v1/persons/42`.
24
+ *
25
+ * Delegates to the shared helper so this module and the plan guard cannot
26
+ * disagree about what an endpointURL's path is — the shared version also
27
+ * restores `{param}` placeholders that `new URL()` percent-encodes, which
28
+ * patterns are authored with. */
29
+ export declare function extractPathname(endpointURL: string | undefined): string | undefined;
30
+ /** True when a resolved config would contribute no query params at all.
31
+ * Both call sites skip the merge entirely in that case, so an empty
32
+ * declaration leaves queryParams untouched rather than setting it to an empty
33
+ * value. Defined once so the two sites cannot drift on what "empty" means. */
34
+ export declare function contributesNothing(config: ScopedQueryParams): boolean;
35
+ /** Batch-scenario steps carry a bare path that may or may not be rooted. */
36
+ export declare function ensureLeadingSlash(p: string): string;
37
+ /** Rank for most-specific-wins: length of the literal prefix before the first
38
+ * wildcard, computed on the ORIGINAL pattern so `{id}` still counts. */
39
+ export declare function patternSpecificity(pathPattern: string): number;
40
+ /**
41
+ * Validate and coerce the raw `api.queryParamOverrides` value read from YAML.
42
+ *
43
+ * The read path in workspaceAuth.ts parses workspace.yml directly rather than
44
+ * through the Zod schema (the walk-up read must stay cheap), so nothing has
45
+ * validated this yet. Malformed entries are dropped with a warning instead of
46
+ * failing generation. Called once per load, not per request.
47
+ */
48
+ export declare function normalizeOverrides(raw: unknown): QueryParamOverride[];
49
+ /**
50
+ * Validate and coerce the raw `api.defaultQueryParams` value read from YAML.
51
+ *
52
+ * Same reason `normalizeOverrides` exists: this read path deliberately bypasses
53
+ * the Zod schema, so nothing has checked the shape. Without this, a scalar
54
+ * declaration (`defaultQueryParams: just-a-string`) is spread by key and yields
55
+ * one query param per character — `0=j&1=u&2=s&…` sent to the server. A list
56
+ * behaves the same way, and a map with non-string values would reach
57
+ * mergeQueryParams* and be dropped there with a less specific warning.
58
+ *
59
+ * Anything that is not a plain object of string values degrades to an empty
60
+ * base map with a warning naming what was found.
61
+ */
62
+ export declare function normalizeBaseMap(raw: unknown): Record<string, string>;
63
+ /**
64
+ * Effective query params for one request path: the base map with the single
65
+ * most-specific matching override layered on top.
66
+ *
67
+ * Overrides never merge with each other — at most one wins. Ranking is by
68
+ * literal prefix length, then total pattern length, then document order.
69
+ *
70
+ * Whenever the patterns differ in specificity the result is independent of how
71
+ * the YAML list is ordered. Document order is only reached when two patterns
72
+ * tie on BOTH earlier criteria, and reordering the list does change the winner
73
+ * in that case — which is why a tie emits an ambiguity warning naming both
74
+ * patterns rather than resolving silently.
75
+ */
76
+ export declare function resolveQueryParamsForPath(config: ScopedQueryParams, pathname: string): Record<string, string>;
77
+ /** Environment variables this workspace's query params defer to, in declaration
78
+ * order and deduplicated (SKYR-4127).
79
+ *
80
+ * These references resolve where the test RUNS, so under Testbot they resolve
81
+ * inside the executor container — whose environment is a fixed allowlist. A
82
+ * name that never reaches the container resolves to nothing, and an unresolved
83
+ * query param is dropped from the request silently, producing a passing test
84
+ * that exercised the wrong identity. The executor uses this list to forward
85
+ * exactly what the workspace asked for.
86
+ *
87
+ * Deliberately derived from the declarations rather than from the ambient
88
+ * environment: nothing is forwarded that workspace.yml did not name.
89
+ *
90
+ * Both maps are read even though `defaultQueryParams` documents itself as
91
+ * literal-only — that is a doc contract the core loader does not enforce, and
92
+ * a declaration there would otherwise reproduce the original silent failure. */
93
+ export declare function extractEnvVarNames(config: ScopedQueryParams): string[];
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Path-scoped resolution for workspace-declared query params (SKYR-4127).
3
+ *
4
+ * SKYR-4050 shipped a single flat `api.defaultQueryParams` map applied
5
+ * identically to every generated request. That is too coarse for APIs whose
6
+ * authorization context is per-endpoint (e.g. an RBAC role validated against a
7
+ * different allow-list per path). `api.queryParamOverrides` layers path-scoped
8
+ * values on top of that base map.
9
+ *
10
+ * Deliberately free of filesystem access so it unit-tests directly — reading
11
+ * workspace.yml lives in utils/workspaceAuth.ts.
12
+ */
13
+ import picomatch from "picomatch";
14
+ import { logger } from "../utils/logger.js";
15
+ import { pathFromEndpointURL } from "../utils/urlPath.js";
16
+ /** Characters that terminate a pattern's literal prefix.
17
+ *
18
+ * `{` counts because an OpenAPI-style `{id}` placeholder becomes `*` before
19
+ * matching. `[` and `(` count because picomatch also treats character classes
20
+ * (`[0-9]`) and extglobs (`+(a|b)`) as wildcards — without them a pattern like
21
+ * `/v1/users/[0-9]*` would be ranked as though `[` were literal, scoring it
22
+ * more specific than it is and letting it beat a genuinely narrower pattern. */
23
+ const WILDCARD_CHARS = ["*", "?", "{", "[", "("];
24
+ /** Pathname of a full endpoint URL, or undefined if unparseable.
25
+ * `endpointURL` is contractually base URL + path, so the result includes any
26
+ * base-path prefix: `https://host/api/v1/persons/42` -> `/api/v1/persons/42`.
27
+ *
28
+ * Delegates to the shared helper so this module and the plan guard cannot
29
+ * disagree about what an endpointURL's path is — the shared version also
30
+ * restores `{param}` placeholders that `new URL()` percent-encodes, which
31
+ * patterns are authored with. */
32
+ export function extractPathname(endpointURL) {
33
+ return pathFromEndpointURL(endpointURL);
34
+ }
35
+ /** True when a resolved config would contribute no query params at all.
36
+ * Both call sites skip the merge entirely in that case, so an empty
37
+ * declaration leaves queryParams untouched rather than setting it to an empty
38
+ * value. Defined once so the two sites cannot drift on what "empty" means. */
39
+ export function contributesNothing(config) {
40
+ return Object.keys(config.base).length === 0 && config.overrides.length === 0;
41
+ }
42
+ /** Batch-scenario steps carry a bare path that may or may not be rooted. */
43
+ export function ensureLeadingSlash(p) {
44
+ return p.startsWith("/") ? p : `/${p}`;
45
+ }
46
+ /** Rank for most-specific-wins: length of the literal prefix before the first
47
+ * wildcard, computed on the ORIGINAL pattern so `{id}` still counts. */
48
+ export function patternSpecificity(pathPattern) {
49
+ let earliest = pathPattern.length;
50
+ for (const ch of WILDCARD_CHARS) {
51
+ const idx = pathPattern.indexOf(ch);
52
+ if (idx !== -1 && idx < earliest)
53
+ earliest = idx;
54
+ }
55
+ return earliest;
56
+ }
57
+ /** Rewrite OpenAPI-style `{param}` placeholders to a single-segment wildcard.
58
+ * Without this, picomatch reads `{id}` as brace alternation and matches only
59
+ * the literal string `/v1/persons/id`. Brace alternation is therefore not
60
+ * supported in patterns — an accepted trade. */
61
+ function toGlob(pathPattern) {
62
+ return pathPattern.replace(/\{[^/}]*\}/g, "*");
63
+ }
64
+ function isStringMap(v) {
65
+ if (!v || typeof v !== "object" || Array.isArray(v))
66
+ return false;
67
+ return Object.values(v).every((x) => typeof x === "string");
68
+ }
69
+ /**
70
+ * Validate and coerce the raw `api.queryParamOverrides` value read from YAML.
71
+ *
72
+ * The read path in workspaceAuth.ts parses workspace.yml directly rather than
73
+ * through the Zod schema (the walk-up read must stay cheap), so nothing has
74
+ * validated this yet. Malformed entries are dropped with a warning instead of
75
+ * failing generation. Called once per load, not per request.
76
+ */
77
+ export function normalizeOverrides(raw) {
78
+ if (!Array.isArray(raw)) {
79
+ if (raw !== undefined) {
80
+ logger.warning("Ignoring api.queryParamOverrides — expected a list", { got: typeof raw });
81
+ }
82
+ return [];
83
+ }
84
+ const result = [];
85
+ for (const entry of raw) {
86
+ const e = entry;
87
+ if (!e || typeof e !== "object" || typeof e.pathPattern !== "string" || e.pathPattern.length === 0) {
88
+ logger.warning("Skipping api.queryParamOverrides entry with a missing or empty pathPattern");
89
+ continue;
90
+ }
91
+ if (!isStringMap(e.values)) {
92
+ logger.warning("Skipping api.queryParamOverrides entry whose values are not a string map", {
93
+ pathPattern: e.pathPattern,
94
+ });
95
+ continue;
96
+ }
97
+ result.push({ pathPattern: e.pathPattern, values: e.values });
98
+ }
99
+ return result;
100
+ }
101
+ /**
102
+ * Validate and coerce the raw `api.defaultQueryParams` value read from YAML.
103
+ *
104
+ * Same reason `normalizeOverrides` exists: this read path deliberately bypasses
105
+ * the Zod schema, so nothing has checked the shape. Without this, a scalar
106
+ * declaration (`defaultQueryParams: just-a-string`) is spread by key and yields
107
+ * one query param per character — `0=j&1=u&2=s&…` sent to the server. A list
108
+ * behaves the same way, and a map with non-string values would reach
109
+ * mergeQueryParams* and be dropped there with a less specific warning.
110
+ *
111
+ * Anything that is not a plain object of string values degrades to an empty
112
+ * base map with a warning naming what was found.
113
+ */
114
+ export function normalizeBaseMap(raw) {
115
+ if (raw === undefined || raw === null)
116
+ return {};
117
+ if (isStringMap(raw))
118
+ return raw;
119
+ logger.warning("Ignoring api.defaultQueryParams — expected a map of string values", { got: Array.isArray(raw) ? "array" : typeof raw });
120
+ return {};
121
+ }
122
+ /**
123
+ * Effective query params for one request path: the base map with the single
124
+ * most-specific matching override layered on top.
125
+ *
126
+ * Overrides never merge with each other — at most one wins. Ranking is by
127
+ * literal prefix length, then total pattern length, then document order.
128
+ *
129
+ * Whenever the patterns differ in specificity the result is independent of how
130
+ * the YAML list is ordered. Document order is only reached when two patterns
131
+ * tie on BOTH earlier criteria, and reordering the list does change the winner
132
+ * in that case — which is why a tie emits an ambiguity warning naming both
133
+ * patterns rather than resolving silently.
134
+ */
135
+ export function resolveQueryParamsForPath(config, pathname) {
136
+ const matches = config.overrides
137
+ .map((override, index) => ({ override, index }))
138
+ .filter(({ override }) => picomatch.isMatch(pathname, toGlob(override.pathPattern)));
139
+ if (matches.length === 0)
140
+ return { ...config.base };
141
+ matches.sort((a, b) => {
142
+ const specDiff = patternSpecificity(b.override.pathPattern) - patternSpecificity(a.override.pathPattern);
143
+ if (specDiff !== 0)
144
+ return specDiff;
145
+ // Known limitation (Finding 5, SKYR-4127 final review): at equal literal-
146
+ // prefix length, this tiebreak prefers the LONGER total pattern — which
147
+ // is not always the narrower one. E.g. "/v1/persons/**" (14 chars) beats
148
+ // "/v1/persons/*" (13 chars) even though "**" matches strictly more paths
149
+ // than "*". Length is a proxy for specificity, not a guarantee of it; not
150
+ // fixed here since it would change ranking behavior for existing configs.
151
+ const lenDiff = b.override.pathPattern.length - a.override.pathPattern.length;
152
+ if (lenDiff !== 0)
153
+ return lenDiff;
154
+ return a.index - b.index;
155
+ });
156
+ const [winner, runnerUp] = matches;
157
+ if (runnerUp &&
158
+ patternSpecificity(winner.override.pathPattern) === patternSpecificity(runnerUp.override.pathPattern) &&
159
+ winner.override.pathPattern.length === runnerUp.override.pathPattern.length) {
160
+ logger.warning("Ambiguous api.queryParamOverrides — two equally specific patterns match this path; using the first", {
161
+ pathname,
162
+ patterns: [winner.override.pathPattern, runnerUp.override.pathPattern],
163
+ });
164
+ }
165
+ return { ...config.base, ...winner.override.values };
166
+ }
167
+ /** `env.VAR` / legacy `envs.VAR`, anchored so a literal that merely contains
168
+ * the prefix (`prod.env.HOST`) is not mistaken for a reference, and requiring
169
+ * at least one character of variable name. Case-sensitive, matching the
170
+ * schema's documented behavior: `ENV.VAR` is an ordinary literal. */
171
+ const ENV_REFERENCE = /^envs?\.(.+)$/;
172
+ /** Environment variables this workspace's query params defer to, in declaration
173
+ * order and deduplicated (SKYR-4127).
174
+ *
175
+ * These references resolve where the test RUNS, so under Testbot they resolve
176
+ * inside the executor container — whose environment is a fixed allowlist. A
177
+ * name that never reaches the container resolves to nothing, and an unresolved
178
+ * query param is dropped from the request silently, producing a passing test
179
+ * that exercised the wrong identity. The executor uses this list to forward
180
+ * exactly what the workspace asked for.
181
+ *
182
+ * Deliberately derived from the declarations rather than from the ambient
183
+ * environment: nothing is forwarded that workspace.yml did not name.
184
+ *
185
+ * Both maps are read even though `defaultQueryParams` documents itself as
186
+ * literal-only — that is a doc contract the core loader does not enforce, and
187
+ * a declaration there would otherwise reproduce the original silent failure. */
188
+ export function extractEnvVarNames(config) {
189
+ const names = new Set();
190
+ const collect = (values) => {
191
+ for (const value of Object.values(values)) {
192
+ const match = typeof value === "string" ? ENV_REFERENCE.exec(value) : null;
193
+ if (match)
194
+ names.add(match[1]);
195
+ }
196
+ };
197
+ collect(config.base);
198
+ for (const override of config.overrides)
199
+ collect(override.values);
200
+ return [...names];
201
+ }
@@ -19,6 +19,34 @@ export declare const serviceSchema: z.ZodObject<{
19
19
  * Explicit per-request queryParams always win on a key collision
20
20
  * (SKYR-4050). */
21
21
  defaultQueryParams: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
22
+ /** Path-scoped overrides layered on top of defaultQueryParams
23
+ * (SKYR-4127). For a given request, the single most-specific matching
24
+ * pathPattern wins and its values override the base map key by key;
25
+ * overrides never merge with each other. Patterns are picomatch globs
26
+ * matched against the request URL pathname, with OpenAPI-style
27
+ * `{param}` placeholders treated as a single-segment wildcard.
28
+ *
29
+ * The pathname includes any base-path prefix carried by baseUrl — a
30
+ * service at `https://host/api` needs `/api/v1/**`, not `/v1/**`. A
31
+ * pattern that omits the prefix silently matches nothing.
32
+ *
33
+ * Values may use `env.VAR` to defer to an environment variable read
34
+ * where the test runs rather than where it was generated — the
35
+ * generated test carries the lookup itself (os.getenv in Python,
36
+ * process.env in JS/TS, System.getenv in Java). The legacy `envs.`
37
+ * spelling is still accepted. Note the variable name is passed
38
+ * through verbatim and is case-sensitive; a near miss such as
39
+ * `ENV.VAR` is treated as an ordinary literal. */
40
+ queryParamOverrides: z.ZodOptional<z.ZodArray<z.ZodObject<{
41
+ pathPattern: z.ZodString;
42
+ values: z.ZodRecord<z.ZodString, z.ZodString>;
43
+ }, "strict", z.ZodTypeAny, {
44
+ values: Record<string, string>;
45
+ pathPattern: string;
46
+ }, {
47
+ values: Record<string, string>;
48
+ pathPattern: string;
49
+ }>, "many">>;
22
50
  baseUrl: z.ZodOptional<z.ZodString>;
23
51
  /** SUT serves self-signed TLS (e.g. a kind kube-apiserver) — generated
24
52
  * tests should skip certificate verification for this service (SKYR-3961).
@@ -30,6 +58,10 @@ export declare const serviceSchema: z.ZodObject<{
30
58
  schemaPath?: string | undefined;
31
59
  authType?: "none" | "token" | "bearer" | "basic" | "oauth" | "apiKey" | "cookie" | "session" | undefined;
32
60
  defaultQueryParams?: Record<string, string> | undefined;
61
+ queryParamOverrides?: {
62
+ values: Record<string, string>;
63
+ pathPattern: string;
64
+ }[] | undefined;
33
65
  baseUrl?: string | undefined;
34
66
  skipTLSVerify?: boolean | undefined;
35
67
  }, {
@@ -38,6 +70,10 @@ export declare const serviceSchema: z.ZodObject<{
38
70
  schemaPath?: string | undefined;
39
71
  authType?: "none" | "token" | "bearer" | "basic" | "oauth" | "apiKey" | "cookie" | "session" | undefined;
40
72
  defaultQueryParams?: Record<string, string> | undefined;
73
+ queryParamOverrides?: {
74
+ values: Record<string, string>;
75
+ pathPattern: string;
76
+ }[] | undefined;
41
77
  baseUrl?: string | undefined;
42
78
  skipTLSVerify?: boolean | undefined;
43
79
  }>>;
@@ -114,6 +150,10 @@ export declare const serviceSchema: z.ZodObject<{
114
150
  schemaPath?: string | undefined;
115
151
  authType?: "none" | "token" | "bearer" | "basic" | "oauth" | "apiKey" | "cookie" | "session" | undefined;
116
152
  defaultQueryParams?: Record<string, string> | undefined;
153
+ queryParamOverrides?: {
154
+ values: Record<string, string>;
155
+ pathPattern: string;
156
+ }[] | undefined;
117
157
  baseUrl?: string | undefined;
118
158
  skipTLSVerify?: boolean | undefined;
119
159
  } | undefined;
@@ -145,6 +185,10 @@ export declare const serviceSchema: z.ZodObject<{
145
185
  schemaPath?: string | undefined;
146
186
  authType?: "none" | "token" | "bearer" | "basic" | "oauth" | "apiKey" | "cookie" | "session" | undefined;
147
187
  defaultQueryParams?: Record<string, string> | undefined;
188
+ queryParamOverrides?: {
189
+ values: Record<string, string>;
190
+ pathPattern: string;
191
+ }[] | undefined;
148
192
  baseUrl?: string | undefined;
149
193
  skipTLSVerify?: boolean | undefined;
150
194
  } | undefined;
@@ -212,6 +256,34 @@ export declare const workspaceConfigSchema: z.ZodObject<{
212
256
  * Explicit per-request queryParams always win on a key collision
213
257
  * (SKYR-4050). */
214
258
  defaultQueryParams: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
259
+ /** Path-scoped overrides layered on top of defaultQueryParams
260
+ * (SKYR-4127). For a given request, the single most-specific matching
261
+ * pathPattern wins and its values override the base map key by key;
262
+ * overrides never merge with each other. Patterns are picomatch globs
263
+ * matched against the request URL pathname, with OpenAPI-style
264
+ * `{param}` placeholders treated as a single-segment wildcard.
265
+ *
266
+ * The pathname includes any base-path prefix carried by baseUrl — a
267
+ * service at `https://host/api` needs `/api/v1/**`, not `/v1/**`. A
268
+ * pattern that omits the prefix silently matches nothing.
269
+ *
270
+ * Values may use `env.VAR` to defer to an environment variable read
271
+ * where the test runs rather than where it was generated — the
272
+ * generated test carries the lookup itself (os.getenv in Python,
273
+ * process.env in JS/TS, System.getenv in Java). The legacy `envs.`
274
+ * spelling is still accepted. Note the variable name is passed
275
+ * through verbatim and is case-sensitive; a near miss such as
276
+ * `ENV.VAR` is treated as an ordinary literal. */
277
+ queryParamOverrides: z.ZodOptional<z.ZodArray<z.ZodObject<{
278
+ pathPattern: z.ZodString;
279
+ values: z.ZodRecord<z.ZodString, z.ZodString>;
280
+ }, "strict", z.ZodTypeAny, {
281
+ values: Record<string, string>;
282
+ pathPattern: string;
283
+ }, {
284
+ values: Record<string, string>;
285
+ pathPattern: string;
286
+ }>, "many">>;
215
287
  baseUrl: z.ZodOptional<z.ZodString>;
216
288
  /** SUT serves self-signed TLS (e.g. a kind kube-apiserver) — generated
217
289
  * tests should skip certificate verification for this service (SKYR-3961).
@@ -223,6 +295,10 @@ export declare const workspaceConfigSchema: z.ZodObject<{
223
295
  schemaPath?: string | undefined;
224
296
  authType?: "none" | "token" | "bearer" | "basic" | "oauth" | "apiKey" | "cookie" | "session" | undefined;
225
297
  defaultQueryParams?: Record<string, string> | undefined;
298
+ queryParamOverrides?: {
299
+ values: Record<string, string>;
300
+ pathPattern: string;
301
+ }[] | undefined;
226
302
  baseUrl?: string | undefined;
227
303
  skipTLSVerify?: boolean | undefined;
228
304
  }, {
@@ -231,6 +307,10 @@ export declare const workspaceConfigSchema: z.ZodObject<{
231
307
  schemaPath?: string | undefined;
232
308
  authType?: "none" | "token" | "bearer" | "basic" | "oauth" | "apiKey" | "cookie" | "session" | undefined;
233
309
  defaultQueryParams?: Record<string, string> | undefined;
310
+ queryParamOverrides?: {
311
+ values: Record<string, string>;
312
+ pathPattern: string;
313
+ }[] | undefined;
234
314
  baseUrl?: string | undefined;
235
315
  skipTLSVerify?: boolean | undefined;
236
316
  }>>;
@@ -307,6 +387,10 @@ export declare const workspaceConfigSchema: z.ZodObject<{
307
387
  schemaPath?: string | undefined;
308
388
  authType?: "none" | "token" | "bearer" | "basic" | "oauth" | "apiKey" | "cookie" | "session" | undefined;
309
389
  defaultQueryParams?: Record<string, string> | undefined;
390
+ queryParamOverrides?: {
391
+ values: Record<string, string>;
392
+ pathPattern: string;
393
+ }[] | undefined;
310
394
  baseUrl?: string | undefined;
311
395
  skipTLSVerify?: boolean | undefined;
312
396
  } | undefined;
@@ -338,6 +422,10 @@ export declare const workspaceConfigSchema: z.ZodObject<{
338
422
  schemaPath?: string | undefined;
339
423
  authType?: "none" | "token" | "bearer" | "basic" | "oauth" | "apiKey" | "cookie" | "session" | undefined;
340
424
  defaultQueryParams?: Record<string, string> | undefined;
425
+ queryParamOverrides?: {
426
+ values: Record<string, string>;
427
+ pathPattern: string;
428
+ }[] | undefined;
341
429
  baseUrl?: string | undefined;
342
430
  skipTLSVerify?: boolean | undefined;
343
431
  } | undefined;
@@ -382,6 +470,10 @@ export declare const workspaceConfigSchema: z.ZodObject<{
382
470
  schemaPath?: string | undefined;
383
471
  authType?: "none" | "token" | "bearer" | "basic" | "oauth" | "apiKey" | "cookie" | "session" | undefined;
384
472
  defaultQueryParams?: Record<string, string> | undefined;
473
+ queryParamOverrides?: {
474
+ values: Record<string, string>;
475
+ pathPattern: string;
476
+ }[] | undefined;
385
477
  baseUrl?: string | undefined;
386
478
  skipTLSVerify?: boolean | undefined;
387
479
  } | undefined;
@@ -426,6 +518,10 @@ export declare const workspaceConfigSchema: z.ZodObject<{
426
518
  schemaPath?: string | undefined;
427
519
  authType?: "none" | "token" | "bearer" | "basic" | "oauth" | "apiKey" | "cookie" | "session" | undefined;
428
520
  defaultQueryParams?: Record<string, string> | undefined;
521
+ queryParamOverrides?: {
522
+ values: Record<string, string>;
523
+ pathPattern: string;
524
+ }[] | undefined;
429
525
  baseUrl?: string | undefined;
430
526
  skipTLSVerify?: boolean | undefined;
431
527
  } | undefined;
@@ -479,6 +575,10 @@ export declare function validateWorkspaceConfig(config: unknown): z.SafeParseRet
479
575
  schemaPath?: string | undefined;
480
576
  authType?: "none" | "token" | "bearer" | "basic" | "oauth" | "apiKey" | "cookie" | "session" | undefined;
481
577
  defaultQueryParams?: Record<string, string> | undefined;
578
+ queryParamOverrides?: {
579
+ values: Record<string, string>;
580
+ pathPattern: string;
581
+ }[] | undefined;
482
582
  baseUrl?: string | undefined;
483
583
  skipTLSVerify?: boolean | undefined;
484
584
  } | undefined;
@@ -523,6 +623,10 @@ export declare function validateWorkspaceConfig(config: unknown): z.SafeParseRet
523
623
  schemaPath?: string | undefined;
524
624
  authType?: "none" | "token" | "bearer" | "basic" | "oauth" | "apiKey" | "cookie" | "session" | undefined;
525
625
  defaultQueryParams?: Record<string, string> | undefined;
626
+ queryParamOverrides?: {
627
+ values: Record<string, string>;
628
+ pathPattern: string;
629
+ }[] | undefined;
526
630
  baseUrl?: string | undefined;
527
631
  skipTLSVerify?: boolean | undefined;
528
632
  } | undefined;
@@ -61,6 +61,30 @@ export const serviceSchema = z
61
61
  * Explicit per-request queryParams always win on a key collision
62
62
  * (SKYR-4050). */
63
63
  defaultQueryParams: z.record(z.string()).optional(),
64
+ /** Path-scoped overrides layered on top of defaultQueryParams
65
+ * (SKYR-4127). For a given request, the single most-specific matching
66
+ * pathPattern wins and its values override the base map key by key;
67
+ * overrides never merge with each other. Patterns are picomatch globs
68
+ * matched against the request URL pathname, with OpenAPI-style
69
+ * `{param}` placeholders treated as a single-segment wildcard.
70
+ *
71
+ * The pathname includes any base-path prefix carried by baseUrl — a
72
+ * service at `https://host/api` needs `/api/v1/**`, not `/v1/**`. A
73
+ * pattern that omits the prefix silently matches nothing.
74
+ *
75
+ * Values may use `env.VAR` to defer to an environment variable read
76
+ * where the test runs rather than where it was generated — the
77
+ * generated test carries the lookup itself (os.getenv in Python,
78
+ * process.env in JS/TS, System.getenv in Java). The legacy `envs.`
79
+ * spelling is still accepted. Note the variable name is passed
80
+ * through verbatim and is case-sensitive; a near miss such as
81
+ * `ENV.VAR` is treated as an ordinary literal. */
82
+ queryParamOverrides: z
83
+ .array(z.object({
84
+ pathPattern: z.string().min(1),
85
+ values: z.record(z.string()),
86
+ }).strict())
87
+ .optional(),
64
88
  baseUrl: z.string().optional(),
65
89
  /** SUT serves self-signed TLS (e.g. a kind kube-apiserver) — generated
66
90
  * tests should skip certificate verification for this service (SKYR-3961).