(input: {
readonly ctx: PluginCtx<OpenapiStore>;
readonly integration: IntegrationRecord;
readonly credential: ToolInvocationCredential;
readonly spec?: HealthCheckSpec;
readonly httpClientLayer: Layer.Layer<HttpClient.HttpClient, never, never>;
})
| 783 | * reasons: a rejected credential is a `HealthCheckResult` with |
| 784 | * `status: "expired"`, not an error. */ |
| 785 | export const checkHealthOpenApi = (input: { |
| 786 | readonly ctx: PluginCtx<OpenapiStore>; |
| 787 | readonly integration: IntegrationRecord; |
| 788 | readonly credential: ToolInvocationCredential; |
| 789 | readonly spec?: HealthCheckSpec; |
| 790 | readonly httpClientLayer: Layer.Layer<HttpClient.HttpClient, never, never>; |
| 791 | }): Effect.Effect<HealthCheckResult, StorageFailure> => |
| 792 | Effect.gen(function* () { |
| 793 | const checkedAt = Date.now(); |
| 794 | const config = decodeOpenApiIntegrationConfig(input.integration.config); |
| 795 | const spec = input.spec; |
| 796 | if (!spec) { |
| 797 | return { |
| 798 | status: "unknown", |
| 799 | checkedAt, |
| 800 | detail: "No health check configured.", |
| 801 | } satisfies HealthCheckResult; |
| 802 | } |
| 803 | |
| 804 | const integration = String(input.integration.slug); |
| 805 | const binding = yield* resolveHealthCheckBinding( |
| 806 | input.ctx, |
| 807 | integration, |
| 808 | spec.operation, |
| 809 | config, |
| 810 | ); |
| 811 | if (!binding) { |
| 812 | return { |
| 813 | status: "unknown", |
| 814 | checkedAt, |
| 815 | detail: `Health check operation "${spec.operation}" not found on "${integration}".`, |
| 816 | } satisfies HealthCheckResult; |
| 817 | } |
| 818 | |
| 819 | // HARD block, not just a ranking hint: a health check runs unattended and |
| 820 | // repeatedly, so a mutating operation must never execute through it. The |
| 821 | // normal tool path gates these behind approval, and this path has no |
| 822 | // approval step. The candidate list labels these "(writes)"; refusing here |
| 823 | // is the enforcement. |
| 824 | if (REQUIRE_APPROVAL.has(binding.method.toLowerCase())) { |
| 825 | return { |
| 826 | status: "unknown", |
| 827 | checkedAt, |
| 828 | detail: `Health check operation "${spec.operation}" is a ${binding.method.toUpperCase()} (mutating): pick a read-only operation.`, |
| 829 | } satisfies HealthCheckResult; |
| 830 | } |
| 831 | |
| 832 | const headers: Record<string, string> = { ...(config?.headers ?? {}) }; |
| 833 | const queryParams: Record<string, string> = { ...(config?.queryParams ?? {}) }; |
| 834 | |
| 835 | const template = (config?.authenticationTemplate ?? []).find( |
| 836 | (entry) => String(entry.slug) === String(input.credential.template), |
| 837 | ); |
| 838 | if (template) { |
| 839 | const missing = requiredTemplateVariables(template).filter((name) => { |
| 840 | const value = input.credential.values[name]; |
| 841 | return value == null || value === ""; |
| 842 | }); |
no test coverage detected