diff --git a/packages/plugins/openapi/src/sdk/plugin.test.ts b/packages/plugins/openapi/src/sdk/plugin.test.ts index 2eca162ad..c62df0007 100644 --- a/packages/plugins/openapi/src/sdk/plugin.test.ts +++ b/packages/plugins/openapi/src/sdk/plugin.test.ts @@ -549,6 +549,57 @@ layer(TestLayer)("OpenAPI Plugin", (it) => { }), ); + it.effect("addSpec without credentialTargetScope defaults to the source's scope", () => + // Regression: config-sync calls addSpec without ever setting + // credentialTargetScope. Before the fix, any source with a + // header secret in executor.jsonc errored with + // "credentialTargetScope is required when adding direct OpenAPI + // credentials" the moment the daemon started. + Effect.gen(function* () { + const httpClient = yield* HttpClient.HttpClient; + const clientLayer = Layer.succeed(HttpClient.HttpClient, httpClient); + + const executor = yield* createExecutor( + makeTestConfig({ + plugins: [ + openApiPlugin({ httpClientLayer: clientLayer }), + memorySecretsPlugin(), + ] as const, + }), + ); + + yield* executor.secrets.set( + new SetSecretInput({ + id: SecretId.make("config-sync-token"), + scope: ScopeId.make(TEST_SCOPE), + name: "Config-sync token", + value: "secret-from-jsonc", + }), + ); + + yield* executor.openapi.addSpec({ + spec: specJson, + scope: TEST_SCOPE, + namespace: "default_target_scope", + baseUrl: "", + headers: { + Authorization: { secretId: "config-sync-token", prefix: "Bearer " }, + }, + }); + + const bindings = yield* executor.openapi.listSourceBindings( + "default_target_scope", + TEST_SCOPE, + ); + expect(bindings).toHaveLength(1); + expect(bindings[0]).toMatchObject({ + scopeId: ScopeId.make(TEST_SCOPE), + slot: "header:authorization", + value: { kind: "secret", secretId: SecretId.make("config-sync-token") }, + }); + }), + ); + it.effect("fails clearly when a secret is missing", () => Effect.gen(function* () { const httpClient = yield* HttpClient.HttpClient; diff --git a/packages/plugins/openapi/src/sdk/plugin.ts b/packages/plugins/openapi/src/sdk/plugin.ts index 34624549c..5f55bb429 100644 --- a/packages/plugins/openapi/src/sdk/plugin.ts +++ b/packages/plugins/openapi/src/sdk/plugin.ts @@ -1004,7 +1004,11 @@ export const openApiPlugin = definePlugin((options?: OpenApiPluginOptions) => { queryParams: config.queryParams, specFetchCredentials: config.specFetchCredentials, oauth2: config.oauth2, - credentialTargetScope: config.credentialTargetScope, + // Default to the source's own scope. refreshSource and editSource + // do the same; without this, config-sync's addSpec — which never + // passes the field — fails with "credentialTargetScope is + // required" the moment the jsonc declares any header secret. + credentialTargetScope: config.credentialTargetScope ?? config.scope, }); });