From bf3e0ed5a1a24d6b53a67adb66ee00507bf00a3c Mon Sep 17 00:00:00 2001 From: Yufei Date: Sun, 5 Oct 2025 23:16:24 -0700 Subject: [PATCH 1/8] Create docs for federation --- .../in-dev/unreleased/federation/_index.md | 26 ++++ .../federation/hive-metastore-federation.md | 125 ++++++++++++++++++ .../federation/iceberg-rest-federation.md | 90 +++++++++++++ 3 files changed, 241 insertions(+) create mode 100644 site/content/in-dev/unreleased/federation/_index.md create mode 100644 site/content/in-dev/unreleased/federation/hive-metastore-federation.md create mode 100644 site/content/in-dev/unreleased/federation/iceberg-rest-federation.md diff --git a/site/content/in-dev/unreleased/federation/_index.md b/site/content/in-dev/unreleased/federation/_index.md new file mode 100644 index 0000000000..e4fbe261a0 --- /dev/null +++ b/site/content/in-dev/unreleased/federation/_index.md @@ -0,0 +1,26 @@ +--- +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +title: Federation +type: docs +weight: 703 +--- + +Guides for federating Polaris with existing metadata services. Expand this section to select a +specific integration. diff --git a/site/content/in-dev/unreleased/federation/hive-metastore-federation.md b/site/content/in-dev/unreleased/federation/hive-metastore-federation.md new file mode 100644 index 0000000000..0d39a5e4a0 --- /dev/null +++ b/site/content/in-dev/unreleased/federation/hive-metastore-federation.md @@ -0,0 +1,125 @@ +--- +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +title: Hive Metastore Federation +type: docs +weight: 705 +--- + +Polaris can federate catalog operations to an existing Hive Metastore (HMS). This lets an external +HMS remain the source of truth for table metadata while Polaris brokers access, policies, and +multi-engine connectivity. + +## Build-time enablement + +The Hive factory is packaged as an optional extension and is not baked into default server builds. +Include it when assembling the runtime or container images by setting the `NonRESTCatalogs` Gradle +property to include `HIVE` (and any other non-REST backends you need): + +```bash +./gradlew :polaris-server:assemble :polaris-server:quarkusAppPartsBuild --rerun \ + -DNonRESTCatalogs=HIVE -Dquarkus.container-image.build=true +``` + +`runtime/server/build.gradle.kts` wires the extension in only when this flag is present, so binaries +built without it will reject Hive federation requests. + +## Runtime requirements + +- **Metastore connectivity:** Expose the HMS Thrift endpoint (`thrift://host:port`) to the Polaris + deployment. +- **Configuration discovery:** Iceberg’s `HiveCatalog` loads Hadoop/Hive client settings from the + classpath. Provide `hive-site.xml` (and `core-site.xml` if needed) via + `HADOOP_CONF_DIR`/`HIVE_CONF_DIR` or an image layer. +- **Authentication:** Hive federation only supports `IMPLICIT` authentication, meaning Polaris uses + the operating-system or Kerberos identity of the running process (no stored secrets). Ensure the + service principal is logged in or holds a valid keytab/TGT before starting Polaris. +- **Object storage role:** Configure `polaris.service-identity..aws-iam.*` (or the default + realm) so the server can assume the AWS role referenced by the catalog. The IAM role must allow + STS access from the Polaris service identity and grant permissions to the table locations. + +### Kerberos setup example + +If your Hive Metastore enforces Kerberos, stage the necessary configuration alongside Polaris: + +```bash +export KRB5_CONFIG=/etc/polaris/krb5.conf +export HADOOP_CONF_DIR=/etc/polaris/hadoop-conf # contains hive-site.xml with HMS principal +export HADOOP_OPTS="-Djava.security.auth.login.config=/etc/polaris/jaas.conf" +kinit -kt /etc/polaris/keytabs/polaris.keytab polaris/service@EXAMPLE.COM +``` + +- `hive-site.xml` must define `hive.metastore.sasl.enabled=true`, the metastore principal, and + client principal pattern (for example `hive.metastore.client.kerberos.principal=polaris/_HOST@REALM`). +- The JAAS entry (referenced by `java.security.auth.login.config`) should use `useKeyTab=true` and + point to the same keytab shown above so the Polaris JVM can refresh credentials automatically. +- Keep the keytab readable solely by the Polaris service user; the implicit authenticator consumes + the TGT at startup and for periodic renewal. + +## Creating a federated catalog + +Use the Management API (or the Python CLI) to create an external catalog whose connection type is +`HIVE`. The following request registers a catalog that proxies to an HMS running on +`thrift://hms.example.internal:9083`: + +```bash +curl -X POST https:///management/v1/catalogs \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "EXTERNAL", + "name": "analytics_hms", + "storageConfigInfo": { + "storageType": "S3", + "roleArn": "arn:aws:iam::123456789012:role/polaris-warehouse-access", + "region": "us-east-1" + }, + "properties": { "default-base-location": "s3://analytics-bucket/warehouse/" }, + "connectionConfigInfo": { + "connectionType": "HIVE", + "uri": "thrift://hms.example.internal:9083", + "warehouse": "s3://analytics-bucket/warehouse/", + "authenticationParameters": { "authenticationType": "IMPLICIT" } + } + }' +``` + +Grant catalog roles to principal roles exactly as you would for internal catalogs so engines can +obtain tokens that authorize against the federated metadata. + +`default-base-location` is required; it tells Polaris and Iceberg where to place new metadata files. +`allowedLocations` is optional—supply it only when you want to restrict writers to a specific set of +prefixes. If your IAM trust policy requires an `externalId` or explicit `userArn`, include those +optional fields in `storageConfigInfo`. Polaris persists them and supplies them when assuming the +role cited by `roleArn` during metadata commits. + +## Limitations and operational notes + +- **Single identity:** Because only `IMPLICIT` authentication is permitted, Polaris cannot mix + multiple Hive identities in a single deployment (`HiveFederatedCatalogFactory` rejects other auth + types). Plan a deployment topology that aligns the Polaris process identity with the target HMS. +- **Generic tables:** The Hive extension exposes Iceberg tables registered in HMS. Generic table + federation is not implemented (`HiveFederatedCatalogFactory#createGenericCatalog` throws + `UnsupportedOperationException`). +- **Configuration caching:** Atlas-style catalog failover and multi-HMS routing are not yet handled; + Polaris initializes one `HiveCatalog` per connection and relies on the underlying Iceberg client + for retries. + +With these constraints satisfied, Polaris can sit in front of an HMS so that Iceberg tables managed +there gain OAuth-protected, multi-engine access through the Polaris REST APIs. diff --git a/site/content/in-dev/unreleased/federation/iceberg-rest-federation.md b/site/content/in-dev/unreleased/federation/iceberg-rest-federation.md new file mode 100644 index 0000000000..0612b07241 --- /dev/null +++ b/site/content/in-dev/unreleased/federation/iceberg-rest-federation.md @@ -0,0 +1,90 @@ +--- +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +title: Iceberg REST Federation +type: docs +weight: 704 +--- + +Polaris can front an external Iceberg REST catalog so that existing metadata services (another +Polaris deployment, Nessie, or a custom Iceberg REST implementation) gain OAuth-protected access +control and multi-engine routing through the Polaris API surface. + +## Runtime requirements + +- **REST endpoint:** The remote service must expose the Iceberg REST specification. Configure + firewalls so Polaris can reach the base URI you provide in the connection config. +- **Authentication:** Polaris forwards requests using the credentials defined in + `connectionConfigInfo.authenticationParameters`. OAuth2 client credentials, bearer tokens, and AWS + SigV4 are supported; choose the scheme the remote service expects. +- **Service identity (SigV4 only):** When using SigV4, set `polaris.service-identity..aws-iam.*` + so Polaris can assume the IAM role referenced by the connection’s `serviceIdentity` block. +- **Object storage access:** Polaris still writes Iceberg metadata locally when brokering commits, so + ensure the catalog’s `storageConfigInfo` grants access to the table locations, just as you would for + internal catalogs. + +## Creating a federated REST catalog + +The snippet below registers an external catalog that forwards to a remote Polaris server using OAuth2 +client credentials. `remoteCatalogName` is optional; supply it when the remote server multiplexes +multiple logical catalogs under one URI. + +```bash +curl -X POST https:///management/v1/catalogs \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "type": "EXTERNAL", + "name": "analytics_rest", + "storageConfigInfo": { + "storageType": "S3", + "roleArn": "arn:aws:iam::123456789012:role/polaris-warehouse-access" + }, + "properties": { "default-base-location": "s3://analytics-bucket/warehouse/" }, + "connectionConfigInfo": { + "connectionType": "ICEBERG_REST", + "uri": "https://remote-polaris.example.com/catalog/v1", + "remoteCatalogName": "analytics", + "authenticationParameters": { + "authenticationType": "OAUTH", + "tokenUri": "https://remote-polaris.example.com/catalog/v1/oauth/tokens", + "clientId": "federation-client", + "clientSecret": "", + "scopes": ["PRINCIPAL_ROLE:ALL"] + } + } + }' +``` + +For bearer-token authentication, replace the `authenticationParameters` block with +`{"authenticationType": "BEARER", "bearerToken": ""}`. For SigV4, supply +`{"authenticationType": "SIGV4", ...}` along with an `awsIam` `serviceIdentity` that contains the +role ARN Polaris should assume when signing requests. + +Grant catalog roles to principal roles the same way you do for internal catalogs so compute engines +receive tokens with access to the federated namespace. + +## Operational notes + +- **Connectivity checks:** Polaris does not lazily probe the remote service; catalog creation fails if + the REST endpoint is unreachable or authentication is rejected. +- **Feature parity:** Federation exposes whatever table/namespace operations the remote service + implements. Unsupported features return the remote error directly to callers. +- **Generic tables:** The REST federation path currently surfaces Iceberg tables only; generic table + federation is not implemented. From c56d74aabdcbf238c608e1051ca02151cd11130f Mon Sep 17 00:00:00 2001 From: Yufei Gu Date: Thu, 9 Oct 2025 14:12:24 -0700 Subject: [PATCH 2/8] Resolve comments --- .../in-dev/unreleased/federation/iceberg-rest-federation.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/site/content/in-dev/unreleased/federation/iceberg-rest-federation.md b/site/content/in-dev/unreleased/federation/iceberg-rest-federation.md index 0612b07241..17019578c4 100644 --- a/site/content/in-dev/unreleased/federation/iceberg-rest-federation.md +++ b/site/content/in-dev/unreleased/federation/iceberg-rest-federation.md @@ -35,9 +35,6 @@ control and multi-engine routing through the Polaris API surface. SigV4 are supported; choose the scheme the remote service expects. - **Service identity (SigV4 only):** When using SigV4, set `polaris.service-identity..aws-iam.*` so Polaris can assume the IAM role referenced by the connection’s `serviceIdentity` block. -- **Object storage access:** Polaris still writes Iceberg metadata locally when brokering commits, so - ensure the catalog’s `storageConfigInfo` grants access to the table locations, just as you would for - internal catalogs. ## Creating a federated REST catalog From b9e41146a4224ee38a83712fb9f960fc95ff14b6 Mon Sep 17 00:00:00 2001 From: Yufei Gu Date: Thu, 9 Oct 2025 14:28:32 -0700 Subject: [PATCH 3/8] Resolve comments --- .../federation/iceberg-rest-federation.md | 38 +++++++------------ 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/site/content/in-dev/unreleased/federation/iceberg-rest-federation.md b/site/content/in-dev/unreleased/federation/iceberg-rest-federation.md index 17019578c4..554f805fd5 100644 --- a/site/content/in-dev/unreleased/federation/iceberg-rest-federation.md +++ b/site/content/in-dev/unreleased/federation/iceberg-rest-federation.md @@ -43,30 +43,20 @@ client credentials. `remoteCatalogName` is optional; supply it when the remote s multiple logical catalogs under one URI. ```bash -curl -X POST https:///management/v1/catalogs \ - -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "type": "EXTERNAL", - "name": "analytics_rest", - "storageConfigInfo": { - "storageType": "S3", - "roleArn": "arn:aws:iam::123456789012:role/polaris-warehouse-access" - }, - "properties": { "default-base-location": "s3://analytics-bucket/warehouse/" }, - "connectionConfigInfo": { - "connectionType": "ICEBERG_REST", - "uri": "https://remote-polaris.example.com/catalog/v1", - "remoteCatalogName": "analytics", - "authenticationParameters": { - "authenticationType": "OAUTH", - "tokenUri": "https://remote-polaris.example.com/catalog/v1/oauth/tokens", - "clientId": "federation-client", - "clientSecret": "", - "scopes": ["PRINCIPAL_ROLE:ALL"] - } - } - }' +polaris catalogs create \ + --name analytics_rest \ + --type EXTERNAL \ + --storage-type S3 \ + --role-arn "arn:aws:iam::123456789012:role/polaris-warehouse-access" \ + --default-base-location "s3://analytics-bucket/warehouse/" \ + --catalog-connection-type ICEBERG \ + --catalog-uri "https://remote-polaris.example.com/catalog/v1" \ + --remote-catalog-name analytics \ + --catalog-authentication-type OAUTH \ + --catalog-token-uri "https://remote-polaris.example.com/catalog/v1/oauth/tokens" \ + --catalog-client-id federation-client \ + --catalog-client-secret "" \ + --catalog-client-scopes "PRINCIPAL_ROLE:ALL" ``` For bearer-token authentication, replace the `authenticationParameters` block with From e3a8fed0b3ed4d41a776e0c997d731956d0bff73 Mon Sep 17 00:00:00 2001 From: Yufei Gu Date: Fri, 10 Oct 2025 15:04:45 -0700 Subject: [PATCH 4/8] Resolve comments --- .../federation/iceberg-rest-federation.md | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/site/content/in-dev/unreleased/federation/iceberg-rest-federation.md b/site/content/in-dev/unreleased/federation/iceberg-rest-federation.md index 554f805fd5..f66e209ea0 100644 --- a/site/content/in-dev/unreleased/federation/iceberg-rest-federation.md +++ b/site/content/in-dev/unreleased/federation/iceberg-rest-federation.md @@ -44,19 +44,19 @@ multiple logical catalogs under one URI. ```bash polaris catalogs create \ - --name analytics_rest \ - --type EXTERNAL \ - --storage-type S3 \ - --role-arn "arn:aws:iam::123456789012:role/polaris-warehouse-access" \ - --default-base-location "s3://analytics-bucket/warehouse/" \ - --catalog-connection-type ICEBERG \ - --catalog-uri "https://remote-polaris.example.com/catalog/v1" \ - --remote-catalog-name analytics \ - --catalog-authentication-type OAUTH \ - --catalog-token-uri "https://remote-polaris.example.com/catalog/v1/oauth/tokens" \ - --catalog-client-id federation-client \ - --catalog-client-secret "" \ - --catalog-client-scopes "PRINCIPAL_ROLE:ALL" + --type EXTERNAL \ + --storage-type s3 \ + --role-arn "arn:aws:iam::123456789012:role/polaris-warehouse-access" \ + --default-base-location "s3://analytics-bucket/warehouse/" \ + --catalog-connection-type iceberg-rest \ + --iceberg-remote-catalog-name analytics \ + --catalog-uri "https://remote-polaris.example.com/catalog/v1" \ + --catalog-authentication-type OAUTH \ + --catalog-token-uri "https://remote-polaris.example.com/catalog/v1/oauth/tokens" \ + --catalog-client-id federation-client \ + --catalog-client-secret "" \ + --catalog-client-scopes "PRINCIPAL_ROLE:ALL" \ + analytics_rest ``` For bearer-token authentication, replace the `authenticationParameters` block with From 5c9401d1abcafa0c6494965e878e6cf0b04ac2d4 Mon Sep 17 00:00:00 2001 From: Yufei Gu Date: Fri, 10 Oct 2025 15:19:43 -0700 Subject: [PATCH 5/8] Resolve comments --- .../in-dev/unreleased/federation/iceberg-rest-federation.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/site/content/in-dev/unreleased/federation/iceberg-rest-federation.md b/site/content/in-dev/unreleased/federation/iceberg-rest-federation.md index f66e209ea0..167e7e794e 100644 --- a/site/content/in-dev/unreleased/federation/iceberg-rest-federation.md +++ b/site/content/in-dev/unreleased/federation/iceberg-rest-federation.md @@ -23,7 +23,7 @@ weight: 704 --- Polaris can front an external Iceberg REST catalog so that existing metadata services (another -Polaris deployment, Nessie, or a custom Iceberg REST implementation) gain OAuth-protected access +Polaris deployment, AWS Glue, or any custom Iceberg REST implementation) gain OAuth-protected access control and multi-engine routing through the Polaris API surface. ## Runtime requirements @@ -31,7 +31,7 @@ control and multi-engine routing through the Polaris API surface. - **REST endpoint:** The remote service must expose the Iceberg REST specification. Configure firewalls so Polaris can reach the base URI you provide in the connection config. - **Authentication:** Polaris forwards requests using the credentials defined in - `connectionConfigInfo.authenticationParameters`. OAuth2 client credentials, bearer tokens, and AWS + `ConnectionConfigInfo.AuthenticationParameters`. OAuth2 client credentials, bearer tokens, and AWS SigV4 are supported; choose the scheme the remote service expects. - **Service identity (SigV4 only):** When using SigV4, set `polaris.service-identity..aws-iam.*` so Polaris can assume the IAM role referenced by the connection’s `serviceIdentity` block. @@ -39,7 +39,7 @@ control and multi-engine routing through the Polaris API surface. ## Creating a federated REST catalog The snippet below registers an external catalog that forwards to a remote Polaris server using OAuth2 -client credentials. `remoteCatalogName` is optional; supply it when the remote server multiplexes +client credentials. `iceberg-remote-catalog-name` is optional; supply it when the remote server multiplexes multiple logical catalogs under one URI. ```bash From 204c40b6b1930313f91bcccf9602377d89964f38 Mon Sep 17 00:00:00 2001 From: Yufei Gu Date: Fri, 10 Oct 2025 16:31:15 -0700 Subject: [PATCH 6/8] Resolve comments --- .../unreleased/federation/iceberg-rest-federation.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/site/content/in-dev/unreleased/federation/iceberg-rest-federation.md b/site/content/in-dev/unreleased/federation/iceberg-rest-federation.md index 167e7e794e..4553a8fcc5 100644 --- a/site/content/in-dev/unreleased/federation/iceberg-rest-federation.md +++ b/site/content/in-dev/unreleased/federation/iceberg-rest-federation.md @@ -33,8 +33,6 @@ control and multi-engine routing through the Polaris API surface. - **Authentication:** Polaris forwards requests using the credentials defined in `ConnectionConfigInfo.AuthenticationParameters`. OAuth2 client credentials, bearer tokens, and AWS SigV4 are supported; choose the scheme the remote service expects. -- **Service identity (SigV4 only):** When using SigV4, set `polaris.service-identity..aws-iam.*` - so Polaris can assume the IAM role referenced by the connection’s `serviceIdentity` block. ## Creating a federated REST catalog @@ -59,10 +57,7 @@ polaris catalogs create \ analytics_rest ``` -For bearer-token authentication, replace the `authenticationParameters` block with -`{"authenticationType": "BEARER", "bearerToken": ""}`. For SigV4, supply -`{"authenticationType": "SIGV4", ...}` along with an `awsIam` `serviceIdentity` that contains the -role ARN Polaris should assume when signing requests. +Refer to the [CLI documentation](../command-line-interface.md#catalogs) for details on alternative authentication types such as BEARER or SIGV4. Grant catalog roles to principal roles the same way you do for internal catalogs so compute engines receive tokens with access to the federated namespace. From 9b191a9e05053cbaa2b1618b8c4964c1124e0a11 Mon Sep 17 00:00:00 2001 From: Yufei Gu Date: Fri, 10 Oct 2025 16:33:10 -0700 Subject: [PATCH 7/8] Resolve comments --- .../in-dev/unreleased/federation/iceberg-rest-federation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/site/content/in-dev/unreleased/federation/iceberg-rest-federation.md b/site/content/in-dev/unreleased/federation/iceberg-rest-federation.md index 4553a8fcc5..d680fdae36 100644 --- a/site/content/in-dev/unreleased/federation/iceberg-rest-federation.md +++ b/site/content/in-dev/unreleased/federation/iceberg-rest-federation.md @@ -51,8 +51,8 @@ polaris catalogs create \ --catalog-uri "https://remote-polaris.example.com/catalog/v1" \ --catalog-authentication-type OAUTH \ --catalog-token-uri "https://remote-polaris.example.com/catalog/v1/oauth/tokens" \ - --catalog-client-id federation-client \ - --catalog-client-secret "" \ + --catalog-client-id "" \ + --catalog-client-secret "" \ --catalog-client-scopes "PRINCIPAL_ROLE:ALL" \ analytics_rest ``` From 2c975ed693eaf51c03e4abdc05f263723f91e22e Mon Sep 17 00:00:00 2001 From: Yufei Gu Date: Fri, 10 Oct 2025 16:42:12 -0700 Subject: [PATCH 8/8] Resolve comments --- .../in-dev/unreleased/federation/iceberg-rest-federation.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/site/content/in-dev/unreleased/federation/iceberg-rest-federation.md b/site/content/in-dev/unreleased/federation/iceberg-rest-federation.md index d680fdae36..8318f45095 100644 --- a/site/content/in-dev/unreleased/federation/iceberg-rest-federation.md +++ b/site/content/in-dev/unreleased/federation/iceberg-rest-federation.md @@ -22,9 +22,8 @@ type: docs weight: 704 --- -Polaris can front an external Iceberg REST catalog so that existing metadata services (another -Polaris deployment, AWS Glue, or any custom Iceberg REST implementation) gain OAuth-protected access -control and multi-engine routing through the Polaris API surface. +Polaris can federate an external Iceberg REST catalog (e.g., another Polaris deployment, AWS Glue, or a custom Iceberg +REST implementation), enabling a Polaris service to access table and view entities managed by remote Iceberg REST Catalogs. ## Runtime requirements