From edeb9fe658aaeeb63dbf7c4e6e49423e9c4d8ecb Mon Sep 17 00:00:00 2001 From: Todd Young Date: Tue, 21 Jul 2026 22:18:15 -0400 Subject: [PATCH 01/18] build: add secret-free Docker image for chpl-api Templated server.xml/context.xml resolve DB credentials from container environment variables via Tomcat's EnvironmentPropertySource, and entrypoint.sh materializes the JWK signing key from an env var at startup. The image itself contains no secrets and is identical across every environment. Co-Authored-By: Claude Sonnet 5 --- .gitattributes | 4 + docker/Dockerfile | 54 +++++++ docker/entrypoint.sh | 13 ++ docker/tomcat-conf/catalina.properties | 202 +++++++++++++++++++++++++ docker/tomcat-conf/context.xml | 35 +++++ docker/tomcat-conf/server.xml | 123 +++++++++++++++ 6 files changed, 431 insertions(+) create mode 100644 docker/Dockerfile create mode 100644 docker/entrypoint.sh create mode 100644 docker/tomcat-conf/catalina.properties create mode 100644 docker/tomcat-conf/context.xml create mode 100644 docker/tomcat-conf/server.xml diff --git a/.gitattributes b/.gitattributes index 7c64743bf3..01e4fe34b5 100644 --- a/.gitattributes +++ b/.gitattributes @@ -25,6 +25,10 @@ *.xml text *.yml text +# Scripts that execute inside Linux containers must keep LF endings on every +# platform - a CRLF shebang breaks `docker build`/`docker run` on checkout. +docker/entrypoint.sh eol=lf + # These files are binary and should be left untouched # (binary is a macro for -text -diff) *.class binary diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000000..1ed1a46e9b --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,54 @@ +# Stage 1: Build the WAR file using Maven +FROM maven:3.9-eclipse-temurin-21-alpine AS build +WORKDIR /app + +# Copy the pom.xml files first to leverage Docker layer caching +# This ensures that if only source code changes, dependencies are not re-downloaded +COPY chpl/pom.xml . +COPY chpl/chpl-api/pom.xml chpl-api/ +COPY chpl/chpl-resources/pom.xml chpl-resources/ +COPY chpl/chpl-service/pom.xml chpl-service/ + +# Copy the rest of the project files +COPY chpl/chpl-api/lombok.config chpl-api/lombok.config +COPY chpl/chpl-api/src chpl-api/src +COPY chpl/chpl-resources/src chpl-resources/src +COPY chpl/chpl-service/lombok.config chpl-service/lombok.config +COPY chpl/chpl-service/src chpl-service/src + +RUN mvn clean package -DskipTests + +# Stage 2: Deploy to Tomcat +# +# This image contains no secrets and is identical across every environment +# (dev/qa/stage/production). Tomcat config below only has ${ENV_VAR} +# placeholders (resolved at container startup - see catalina.properties' +# org.apache.tomcat.util.digester.PROPERTY_SOURCE) and the checked-in +# environment.properties/email.properties already mark every sensitive key +# as SECRET, relying on Spring's Environment to prefer OS environment +# variables over those classpath files. See entrypoint.sh for the one +# exception (the JWK signing key, which the app reads from a file path). +# +# Required environment variables at `docker run` time: +# DB_URL, DB_USERNAME, DB_PASSWORD - jdbc/openchpl datasource +# JWK_KEY - contents of the RSA JOSE JWK signing key +# plus every property marked SECRET in +# chpl/chpl-resources/src/main/resources/environment.properties and email.properties +# (e.g. SPRING_REDIS_PASSWORD, COGNITO_SECRETKEY, AZURE_CLIENTSECRET_ONC, JIRA_PASSWORD, ...) +FROM tomcat:11.0.23-jdk21 + +COPY docker/tomcat-conf/server.xml /usr/local/tomcat/conf/server.xml +COPY docker/tomcat-conf/context.xml /usr/local/tomcat/conf/context.xml +COPY docker/tomcat-conf/catalina.properties /usr/local/tomcat/conf/catalina.properties +COPY docker/entrypoint.sh /usr/local/tomcat/bin/entrypoint.sh +RUN chmod +x /usr/local/tomcat/bin/entrypoint.sh + +COPY --from=build /app/chpl-api/target/chpl-service.war /usr/local/tomcat/webapps/chpl-service.war + +# Path (not a secret) where the JWK key gets written by entrypoint.sh +ENV keyLocation=/usr/local/tomcat/conf/JSONRsaJoseJWebKey.txt + +# Expose our custom Tomcat port +EXPOSE 8181 + +ENTRYPOINT ["/usr/local/tomcat/bin/entrypoint.sh"] diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100644 index 0000000000..bc4f691013 --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,13 @@ +#!/bin/sh +# Materializes the JWK signing key from an environment variable into the file +# path chpl-api expects (environment.properties: keyLocation). Everything else +# needed at runtime (DB creds, Cognito/Azure/Jira/Datadog/Redis secrets, etc.) +# is read directly from environment variables by Spring/Tomcat - see +# docker/tomcat-conf/ and chpl/chpl-resources/src/main/resources/environment.properties. +set -e + +if [ -n "$JWK_KEY" ]; then + printf '%s\n' "$JWK_KEY" > /usr/local/tomcat/conf/JSONRsaJoseJWebKey.txt +fi + +exec catalina.sh run diff --git a/docker/tomcat-conf/catalina.properties b/docker/tomcat-conf/catalina.properties new file mode 100644 index 0000000000..91ba487f86 --- /dev/null +++ b/docker/tomcat-conf/catalina.properties @@ -0,0 +1,202 @@ +# 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. + +# +# +# List of comma-separated paths defining the contents of the "common" +# classloader. Prefixes should be used to define what is the repository type. +# Path may be relative to the CATALINA_HOME or CATALINA_BASE path or absolute. +# If left as blank,the JVM system loader will be used as Catalina's "common" +# loader. +# Examples: +# "foo": Add this folder as a class repository +# "foo/*.jar": Add all the JARs of the specified folder as class +# repositories +# "foo/bar.jar": Add bar.jar as a class repository +# +# Note: Values are enclosed in double quotes ("...") in case either the +# ${catalina.base} path or the ${catalina.home} path contains a comma. +# Because double quotes are used for quoting, the double quote character +# may not appear in a path. +common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar" + +# +# List of comma-separated paths defining the contents of the "server" +# classloader. Prefixes should be used to define what is the repository type. +# Path may be relative to the CATALINA_HOME or CATALINA_BASE path or absolute. +# If left as blank, the "common" loader will be used as Catalina's "server" +# loader. +# Examples: +# "foo": Add this folder as a class repository +# "foo/*.jar": Add all the JARs of the specified folder as class +# repositories +# "foo/bar.jar": Add bar.jar as a class repository +# +# Note: Values may be enclosed in double quotes ("...") in case either the +# ${catalina.base} path or the ${catalina.home} path contains a comma. +# Because double quotes are used for quoting, the double quote character +# may not appear in a path. +server.loader= + +# +# List of comma-separated paths defining the contents of the "shared" +# classloader. Prefixes should be used to define what is the repository type. +# Path may be relative to the CATALINA_BASE path or absolute. If left as blank, +# the "common" loader will be used as Catalina's "shared" loader. +# Examples: +# "foo": Add this folder as a class repository +# "foo/*.jar": Add all the JARs of the specified folder as class +# repositories +# "foo/bar.jar": Add bar.jar as a class repository +# Please note that for single jars, e.g. bar.jar, you need the URL form +# starting with file:. +# +# Note: Values may be enclosed in double quotes ("...") in case either the +# ${catalina.base} path or the ${catalina.home} path contains a comma. +# Because double quotes are used for quoting, the double quote character +# may not appear in a path. +shared.loader= + +# Default list of JAR files that should not be scanned using the JarScanner +# functionality. This is typically used to scan JARs for configuration +# information. JARs that do not contain such information may be excluded from +# the scan to speed up the scanning process. This is the default list. JARs on +# this list are excluded from all scans. The list must be a comma separated list +# of JAR file names. +# The list of JARs to skip may be over-ridden at a Context level for individual +# scan types by configuring a JarScanner with a nested JarScanFilter. +# The JARs listed below include: +# - Tomcat Bootstrap JARs +# - Tomcat API JARs +# - Catalina JARs +# - Jasper JARs +# - Tomcat JARs +# - Common non-Tomcat JARs +# - Test JARs (JUnit, Cobertura and dependencies) +tomcat.util.scan.StandardJarScanFilter.jarsToSkip=\ +annotations-api.jar,\ +ant-junit*.jar,\ +ant-launcher*.jar,\ +ant*.jar,\ +asm-*.jar,\ +aspectj*.jar,\ +bcel*.jar,\ +biz.aQute.bnd*.jar,\ +bootstrap.jar,\ +catalina-ant.jar,\ +catalina-ha.jar,\ +catalina-ssi.jar,\ +catalina-storeconfig.jar,\ +catalina-tribes.jar,\ +catalina.jar,\ +cglib-*.jar,\ +cobertura-*.jar,\ +commons-beanutils*.jar,\ +commons-codec*.jar,\ +commons-collections*.jar,\ +commons-compress*.jar,\ +commons-daemon.jar,\ +commons-dbcp*.jar,\ +commons-digester*.jar,\ +commons-fileupload*.jar,\ +commons-httpclient*.jar,\ +commons-io*.jar,\ +commons-lang*.jar,\ +commons-logging*.jar,\ +commons-math*.jar,\ +commons-pool*.jar,\ +derby-*.jar,\ +dom4j-*.jar,\ +easymock-*.jar,\ +ecj-*.jar,\ +el-api.jar,\ +geronimo-spec-jaxrpc*.jar,\ +h2*.jar,\ +ha-api-*.jar,\ +hamcrest-*.jar,\ +hibernate*.jar,\ +httpclient*.jar,\ +icu4j-*.jar,\ +jakartaee-migration-*.jar,\ +jasper-el.jar,\ +jasper.jar,\ +jaspic-api.jar,\ +jaxb-*.jar,\ +jaxen-*.jar,\ +jaxws-rt-*.jar,\ +jdom-*.jar,\ +jetty-*.jar,\ +jmx-tools.jar,\ +jmx.jar,\ +jsp-api.jar,\ +jstl.jar,\ +jta*.jar,\ +junit-*.jar,\ +junit.jar,\ +log4j*.jar,\ +mail*.jar,\ +objenesis-*.jar,\ +oraclepki.jar,\ +org.hamcrest.core_*.jar,\ +org.junit_*.jar,\ +oro-*.jar,\ +servlet-api-*.jar,\ +servlet-api.jar,\ +slf4j*.jar,\ +taglibs-standard-spec-*.jar,\ +tagsoup-*.jar,\ +tomcat-api.jar,\ +tomcat-coyote.jar,\ +tomcat-coyote-ffm.jar,\ +tomcat-dbcp.jar,\ +tomcat-i18n-*.jar,\ +tomcat-jdbc.jar,\ +tomcat-jni.jar,\ +tomcat-juli-adapters.jar,\ +tomcat-juli.jar,\ +tomcat-util-scan.jar,\ +tomcat-util.jar,\ +tomcat-websocket.jar,\ +tools.jar,\ +unboundid-ldapsdk-*.jar,\ +websocket-api.jar,\ +websocket-client-api.jar,\ +wsdl4j*.jar,\ +xercesImpl.jar,\ +xml-apis.jar,\ +xmlParserAPIs-*.jar,\ +xmlParserAPIs.jar,\ +xom-*.jar + +# Default list of JAR files that should be scanned that overrides the default +# jarsToSkip list above. This is typically used to include a specific JAR that +# has been excluded by a broad file name pattern in the jarsToSkip list. +# The list of JARs to scan may be over-ridden at a Context level for individual +# scan types by configuring a JarScanner with a nested JarScanFilter. +tomcat.util.scan.StandardJarScanFilter.jarsToScan=\ +log4j-taglib*.jar,\ +log4j-jakarta-web*.jar,\ +log4javascript*.jar,\ +slf4j-taglib*.jar + +# String cache configuration. +tomcat.util.buf.StringCache.byte.enabled=true +#tomcat.util.buf.StringCache.char.enabled=true +#tomcat.util.buf.StringCache.trainThreshold=500000 +#tomcat.util.buf.StringCache.cacheSize=5000 + +# Enables ${ENV_VAR} substitution in server.xml/context.xml, resolved from +# container environment variables at startup (used for jdbc/openchpl above). +org.apache.tomcat.util.digester.PROPERTY_SOURCE=org.apache.tomcat.util.digester.EnvironmentPropertySource diff --git a/docker/tomcat-conf/context.xml b/docker/tomcat-conf/context.xml new file mode 100644 index 0000000000..8f002a1d2a --- /dev/null +++ b/docker/tomcat-conf/context.xml @@ -0,0 +1,35 @@ + + + + + + + + WEB-INF/web.xml + WEB-INF/tomcat-web.xml + ${catalina.base}/conf/web.xml + + + + + + + diff --git a/docker/tomcat-conf/server.xml b/docker/tomcat-conf/server.xml new file mode 100644 index 0000000000..351f32a2c9 --- /dev/null +++ b/docker/tomcat-conf/server.xml @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 7dbfa2af791a61ff82a43aab5c6057b35efbe702 Mon Sep 17 00:00:00 2001 From: Todd Young Date: Tue, 21 Jul 2026 22:18:38 -0400 Subject: [PATCH 02/18] ci: publish chpl-api Docker image to GHCR Manual workflow_dispatch trigger; builds docker/Dockerfile and pushes to ghcr.io tagged - and -latest. No secrets required at build time. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/docker-publish.yml | 45 ++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 .github/workflows/docker-publish.yml diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml new file mode 100644 index 0000000000..27dce40024 --- /dev/null +++ b/.github/workflows/docker-publish.yml @@ -0,0 +1,45 @@ +name: Publish Docker Image + +on: + workflow_dispatch: {} + +permissions: + contents: read + packages: write + +jobs: + build-and-push: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Compute image metadata + id: meta + run: | + echo "image=ghcr.io/$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT" + echo "sha_short=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT" + echo "branch=$(echo '${{ github.ref_name }}' | tr '/' '-')" >> "$GITHUB_OUTPUT" + + # This image contains no secrets - see docker/Dockerfile. Every environment + # (dev/qa/stage/production) runs the exact same image; secrets are supplied + # as container environment variables at `docker run` time, not baked in here. + - name: Build and push + uses: docker/build-push-action@v6 + with: + context: . + file: docker/Dockerfile + push: true + tags: | + ${{ steps.meta.outputs.image }}:${{ steps.meta.outputs.branch }}-${{ steps.meta.outputs.sha_short }} + ${{ steps.meta.outputs.image }}:${{ steps.meta.outputs.branch }}-latest From 1cf2ee3dd2961dcfeb29143a2b6d3de1a08c903b Mon Sep 17 00:00:00 2001 From: Todd Young Date: Tue, 21 Jul 2026 22:38:38 -0400 Subject: [PATCH 03/18] test: temporarily trigger docker-publish on pull_request workflow_dispatch only appears in the Actions UI once this file exists on the default branch. Adding pull_request lets the workflow be validated on this PR first. Also fixes branch-name resolution for PR events (github.ref_name is '/merge' there, not the branch name). Must be reverted before merging to the default branch. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/docker-publish.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 27dce40024..2e74d60708 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -2,6 +2,10 @@ name: Publish Docker Image on: workflow_dispatch: {} + # TEMPORARY: workflow_dispatch only shows up in the Actions UI once this file + # exists on the default branch, so pull_request lets us test it before that. + # Remove this trigger once the workflow has been validated via a PR. + pull_request: {} permissions: contents: read @@ -29,7 +33,7 @@ jobs: run: | echo "image=ghcr.io/$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT" echo "sha_short=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT" - echo "branch=$(echo '${{ github.ref_name }}' | tr '/' '-')" >> "$GITHUB_OUTPUT" + echo "branch=$(echo '${{ github.head_ref || github.ref_name }}' | tr '/' '-')" >> "$GITHUB_OUTPUT" # This image contains no secrets - see docker/Dockerfile. Every environment # (dev/qa/stage/production) runs the exact same image; secrets are supplied From eafd1a814f0e19f7ca7e0bd75ba99ac2201dfb01 Mon Sep 17 00:00:00 2001 From: Todd Young Date: Tue, 21 Jul 2026 23:01:39 -0400 Subject: [PATCH 04/18] ci: tag chpl-api image with build number, sha, and branch pointer Replaces -/-latest with: build- - immutable, sequential build identifier sha- - immutable, traces to the exact commit latest- - floating pointer to the most recent build from that branch (latest-development, latest-qa, latest-staging, latest-production once triggered from those branches) Co-Authored-By: Claude Sonnet 5 --- .github/workflows/docker-publish.yml | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 2e74d60708..b3f9c19a86 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -36,8 +36,17 @@ jobs: echo "branch=$(echo '${{ github.head_ref || github.ref_name }}' | tr '/' '-')" >> "$GITHUB_OUTPUT" # This image contains no secrets - see docker/Dockerfile. Every environment - # (dev/qa/stage/production) runs the exact same image; secrets are supplied - # as container environment variables at `docker run` time, not baked in here. + # (development/qa/staging/production) runs the exact same image; secrets are + # supplied as container environment variables at `docker run` time, not baked + # in here. + # + # Tags: + # build- - immutable, sequential build number (github.run_number + # never repeats or goes backwards for this workflow) + # sha- - immutable, traces back to the exact commit + # latest- - floating pointer to the most recent build from that + # branch (latest-development, latest-qa, latest-staging, + # latest-production once triggered from those branches) - name: Build and push uses: docker/build-push-action@v6 with: @@ -45,5 +54,6 @@ jobs: file: docker/Dockerfile push: true tags: | - ${{ steps.meta.outputs.image }}:${{ steps.meta.outputs.branch }}-${{ steps.meta.outputs.sha_short }} - ${{ steps.meta.outputs.image }}:${{ steps.meta.outputs.branch }}-latest + ${{ steps.meta.outputs.image }}:build-${{ github.run_number }} + ${{ steps.meta.outputs.image }}:sha-${{ steps.meta.outputs.sha_short }} + ${{ steps.meta.outputs.image }}:latest-${{ steps.meta.outputs.branch }} From f11e1e5b4c5b3dd69a7973211cbd75bb75789144 Mon Sep 17 00:00:00 2001 From: Todd Young Date: Wed, 22 Jul 2026 09:42:27 -0400 Subject: [PATCH 05/18] docs: document required env vars for the chpl-api Docker image Lists every environment variable the secret-free image needs or accepts (DB/JWK required-to-start vars, filesystem-path vars needing a volume mount, environment-dependent non-secret vars, and all 31 properties currently marked SECRET in environment.properties/ email.properties, grouped by feature area). Points at AudaciousInquiry/chpl-build for where real values live today without reproducing any of them. Co-Authored-By: Claude Sonnet 5 --- docker/README.md | 150 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 docker/README.md diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 0000000000..39dfa6957d --- /dev/null +++ b/docker/README.md @@ -0,0 +1,150 @@ +# Running the chpl-api Docker image + +`docker/Dockerfile` builds a **secret-free** image: the same image is used for +every environment (development/qa/staging/production). Nothing sensitive is +baked in at build time - all of it is supplied as container environment +variables when the image is actually run. See `docker/Dockerfile`, +`docker/tomcat-conf/`, and `docker/entrypoint.sh` for how that works +mechanically (Tomcat's `EnvironmentPropertySource` for `server.xml`, Spring's +`Environment` for everything else, and `entrypoint.sh` for the one exception - +the JWK signing key, which the app reads from a file rather than a property). + +This doc lists every environment variable the image needs or accepts. It does +**not** contain real values for any environment - only what each variable is +for and what kind of value it expects. Real values for existing environments +currently live in [AudaciousInquiry/chpl-build](https://github.com/AudaciousInquiry/chpl-build) +(`chpl-build-{dev,qa,stg,prod}/src/main/resources/override-api-properties*.sh`) +- that repo should stay private, and its values should never be copied into +this one. + +## Quick start + +```sh +docker run -d --name chpl-api \ + -p 8181:8181 \ + -e DB_URL="jdbc:postgresql://:5432/openchpl" \ + -e DB_USERNAME="" \ + -e DB_PASSWORD="" \ + -e JWK_KEY="$(cat /path/to/JSONRsaJoseJWebKey.txt)" \ + -e chplUrlBegin="https://chpl-dev.healthit.gov" \ + -e SPRING_REDIS_HOST="" \ + -e SPRING_REDIS_PORT="6379" \ + -e SPRING_REDIS_PASSWORD="" \ + ghcr.io//chpl-api:latest-development +``` + +Add `-e` flags for whichever of the feature-area variables below apply to +your environment - omitted ones just leave that feature unconfigured. + +Note: property keys that contain dots (like `spring.redis.host`) are **not** +valid POSIX environment variable names on every shell/platform - Spring will +also match the all-caps/underscore form (`SPRING_REDIS_HOST`). Use whichever +form your deployment tooling handles more easily; both resolve to the same +property. + +## Required to start at all + +| Env var | Used for | +|---|---| +| `DB_URL` | JDBC URL for the `jdbc/openchpl` datasource (`server.xml`) | +| `DB_USERNAME` | DB username (`server.xml`) | +| `DB_PASSWORD` | DB password (`server.xml`) | +| `JWK_KEY` | Contents of the RSA JOSE JWK signing key. `entrypoint.sh` writes this to `/usr/local/tomcat/conf/JSONRsaJoseJWebKey.txt` at container start (`keyLocation` is already set in the image - you don't need to set it). | + +## Filesystem paths (need a mounted volume, not just an env var) + +These properties are file/directory paths the app reads or writes at runtime. +Mount a volume at the path you set, or the app will fail writing to a +read-only container filesystem. + +| Env var | Property | Purpose | +|---|---|---| +| `downloadFolderPath` | `downloadFolderPath` | Where generated downloadable files are written | +| `auditDataFilePath` | `auditDataFilePath` | Where audit data backups are written | + +## Environment-dependent (not secret, but should differ per environment) + +| Env var | Property | Purpose | Example shape | +|---|---|---|---| +| `chplUrlBegin` | `chplUrlBegin` | Public base URL for this environment | `https://chpl-qa.healthit.gov` | +| `EMAILBUILDER_CONFIG_EMAILSUBJECTSUFFIX` | `emailBuilder.config.emailSubjectSuffix` | Tag appended to outgoing email subjects | `[QA]` | +| `SERVER_ENVIRONMENT` | `server.environment` | `non-production` or `production` | | +| `REPORT_ENVIRONMENT` | `report.environment` | Label used on generated reports | `DEV`, `QA`, `STAGE`, `PROD` | + +## Feature-area secrets + +Every property below is marked `SECRET` in +[`environment.properties`](../chpl/chpl-resources/src/main/resources/environment.properties) +or [`email.properties`](../chpl/chpl-resources/src/main/resources/email.properties) +- meaning the app has no usable default and one of these env vars must be set +for that feature to work. If a feature isn't used in a given environment, its +variables can be omitted. + +**Database / cache** +| Env var | Property | +|---|---| +| `SPRING_REDIS_HOST` | `spring.redis.host` | +| `SPRING_REDIS_PORT` | `spring.redis.port` | +| `SPRING_REDIS_PASSWORD` | `spring.redis.password` | + +**AWS Cognito (authentication)** +| Env var | Property | +|---|---| +| `COGNITO_ACCESSKEY` | `cognito.accessKey` | +| `COGNITO_SECRETKEY` | `cognito.secretKey` | +| `COGNITO_REGION` | `cognito.region` | +| `COGNITO_USERPOOLID` | `cognito.userPoolId` | +| `COGNITO_USERPOOLCLIENTSECRET` | `cognito.userPoolClientSecret` | +| `COGNITO_CLIENTID` | `cognito.clientId` | +| `COGNITO_ENVIRONMENT_GROUPNAME` | `cognito.environment.groupName` | +| `COGNITO_SYSTEMUSERUUID` | `cognito.systemUserUuid` | +| `COGNITO_ANONYMOUSUSERUUID` | `cognito.anonymousUserUuid` | + +**ONC Azure AD** +| Env var | Property | +|---|---| +| `AZURE_USER_ONC` | `azure.user.onc` | +| `AZURE_CLIENTID_ONC` | `azure.clientId.onc` | +| `AZURE_CLIENTSECRET_ONC` | `azure.clientSecret.onc` | +| `AZURE_TENANTID_ONC` | `azure.tenantId.onc` | + +**JIRA** +| Env var | Property | +|---|---| +| `JIRA_USERNAME` | `jira.username` | +| `JIRA_PASSWORD` | `jira.password` | + +**Datadog** +| Env var | Property | +|---|---| +| `DATADOG_APIKEY` | `datadog.apiKey` | +| `DATADOG_APPKEY` | `datadog.appKey` | + +**AIA (Real World Testing validation)** +| Env var | Property | +|---|---| +| `AIA_AUTHENTICATE_CLIENTSECRET` | `aia.authenticate.clientSecret` | +| `AIA_AUTHENTICATE_CLIENTID` | `aia.authenticate.clientId` | + +**FF4J admin console** +| Env var | Property | +|---|---| +| `FF4J_WEBCONSOLE_USERNAME` | `ff4j.webconsole.username` | +| `FF4J_WEBCONSOLE_PASSWORD` | `ff4j.webconsole.password` | + +**Email / notifications** +| Env var | Property | +|---|---| +| `internalErrorEmailRecipients` | `internalErrorEmailRecipients` | +| `internalFutureCertificationStatusEmailRecipients` | `internalFutureCertificationStatusEmailRecipients` | +| `emailBuilder_config_forwardAddress` | `emailBuilder_config_forwardAddress` | +| `DIRECTREVIEW_CHPLCHANGES_EMAIL` | `directReview.chplChanges.email` | +| `DIRECTREVIEW_UNKNOWNCHANGES_EMAIL` | `directReview.unknownChanges.email` | + +## Anything not listed here + +`environment.properties`, `email.properties`, `lookup.properties`, and +`errors.properties` (all in `chpl/chpl-resources/src/main/resources/`) have +sensible non-secret defaults for everything else, and can still be overridden +the same way (env var name = property name, dots -> underscores, upper-cased) +if a particular deployment needs to tune something not listed above. From 5816d9028a2457161b9fdf23923ef9e8b1c2b39c Mon Sep 17 00:00:00 2001 From: Todd Young Date: Wed, 22 Jul 2026 10:22:17 -0400 Subject: [PATCH 06/18] ci: disable provenance/sbom attestations for chpl-api image push docker/build-push-action v6 enables build provenance and SBOM attestations by default, each pushing an extra untagged manifest to GHCR per build (2 per run, with no architecture info, cluttering the package version list). Not needed here, so both are turned off. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/docker-publish.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index b3f9c19a86..9442cebebe 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -53,6 +53,12 @@ jobs: context: . file: docker/Dockerfile push: true + # build-push-action v6 enables these by default, which each push a + # separate untagged provenance/SBOM manifest to GHCR alongside the + # real image - not needed here, so turned off to keep the package + # version list clean. + provenance: false + sbom: false tags: | ${{ steps.meta.outputs.image }}:build-${{ github.run_number }} ${{ steps.meta.outputs.image }}:sha-${{ steps.meta.outputs.sha_short }} From 42578dbabbaf55a1bd065f2dc0446a9edcd8c6e7 Mon Sep 17 00:00:00 2001 From: Todd Young Date: Wed, 22 Jul 2026 12:07:53 -0400 Subject: [PATCH 07/18] Revert "test: temporarily trigger docker-publish on pull_request" This reverts commit 1cf2ee3dd2961dcfeb29143a2b6d3de1a08c903b. --- .github/workflows/docker-publish.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 9442cebebe..c682366620 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -2,10 +2,6 @@ name: Publish Docker Image on: workflow_dispatch: {} - # TEMPORARY: workflow_dispatch only shows up in the Actions UI once this file - # exists on the default branch, so pull_request lets us test it before that. - # Remove this trigger once the workflow has been validated via a PR. - pull_request: {} permissions: contents: read @@ -33,7 +29,7 @@ jobs: run: | echo "image=ghcr.io/$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT" echo "sha_short=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT" - echo "branch=$(echo '${{ github.head_ref || github.ref_name }}' | tr '/' '-')" >> "$GITHUB_OUTPUT" + echo "branch=$(echo '${{ github.ref_name }}' | tr '/' '-')" >> "$GITHUB_OUTPUT" # This image contains no secrets - see docker/Dockerfile. Every environment # (development/qa/staging/production) runs the exact same image; secrets are From 419ec7d863087115cc0c2ccb1f9112227b9bb42d Mon Sep 17 00:00:00 2001 From: Todd Young Date: Wed, 22 Jul 2026 12:12:00 -0400 Subject: [PATCH 08/18] ci: build chpl-api image on merge to environment branches Adds push triggers for development, qa, staging, and production so merging into any of them builds and pushes an image tagged latest-, without needing per-branch copies of the workflow. push triggers are evaluated per-ref and aren't gated by which branch is the repo's default, unlike workflow_dispatch. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/docker-publish.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index c682366620..a554c8e50c 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -2,6 +2,15 @@ name: Publish Docker Image on: workflow_dispatch: {} + # One workflow, shared by every environment branch: a merge into any of + # these triggers a build tagged latest-. No per-branch copies + # or separate workflow files to keep in sync. + push: + branches: + - development + - qa + - staging + - production permissions: contents: read From f9fad67ffb40b4a629972111e0fd49df340009bb Mon Sep 17 00:00:00 2001 From: Todd Young Date: Wed, 22 Jul 2026 23:37:39 -0400 Subject: [PATCH 09/18] chore(docker): remove unused JWK_KEY signing-key mechanism No code reads a keyLocation property or local JWK file anymore - JWT verification goes through Cognito's public JWKS URL. Removing the dead entrypoint.sh materialization step and its references in the Dockerfile, README, and server.xml. --- docker/Dockerfile | 11 +---------- docker/README.md | 11 ++++------- docker/entrypoint.sh | 13 ------------- docker/tomcat-conf/server.xml | 2 +- 4 files changed, 6 insertions(+), 31 deletions(-) delete mode 100644 docker/entrypoint.sh diff --git a/docker/Dockerfile b/docker/Dockerfile index 1ed1a46e9b..b9cc717c53 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -26,12 +26,10 @@ RUN mvn clean package -DskipTests # org.apache.tomcat.util.digester.PROPERTY_SOURCE) and the checked-in # environment.properties/email.properties already mark every sensitive key # as SECRET, relying on Spring's Environment to prefer OS environment -# variables over those classpath files. See entrypoint.sh for the one -# exception (the JWK signing key, which the app reads from a file path). +# variables over those classpath files. # # Required environment variables at `docker run` time: # DB_URL, DB_USERNAME, DB_PASSWORD - jdbc/openchpl datasource -# JWK_KEY - contents of the RSA JOSE JWK signing key # plus every property marked SECRET in # chpl/chpl-resources/src/main/resources/environment.properties and email.properties # (e.g. SPRING_REDIS_PASSWORD, COGNITO_SECRETKEY, AZURE_CLIENTSECRET_ONC, JIRA_PASSWORD, ...) @@ -40,15 +38,8 @@ FROM tomcat:11.0.23-jdk21 COPY docker/tomcat-conf/server.xml /usr/local/tomcat/conf/server.xml COPY docker/tomcat-conf/context.xml /usr/local/tomcat/conf/context.xml COPY docker/tomcat-conf/catalina.properties /usr/local/tomcat/conf/catalina.properties -COPY docker/entrypoint.sh /usr/local/tomcat/bin/entrypoint.sh -RUN chmod +x /usr/local/tomcat/bin/entrypoint.sh COPY --from=build /app/chpl-api/target/chpl-service.war /usr/local/tomcat/webapps/chpl-service.war -# Path (not a secret) where the JWK key gets written by entrypoint.sh -ENV keyLocation=/usr/local/tomcat/conf/JSONRsaJoseJWebKey.txt - # Expose our custom Tomcat port EXPOSE 8181 - -ENTRYPOINT ["/usr/local/tomcat/bin/entrypoint.sh"] diff --git a/docker/README.md b/docker/README.md index 39dfa6957d..fb863b9c96 100644 --- a/docker/README.md +++ b/docker/README.md @@ -3,11 +3,10 @@ `docker/Dockerfile` builds a **secret-free** image: the same image is used for every environment (development/qa/staging/production). Nothing sensitive is baked in at build time - all of it is supplied as container environment -variables when the image is actually run. See `docker/Dockerfile`, -`docker/tomcat-conf/`, and `docker/entrypoint.sh` for how that works -mechanically (Tomcat's `EnvironmentPropertySource` for `server.xml`, Spring's -`Environment` for everything else, and `entrypoint.sh` for the one exception - -the JWK signing key, which the app reads from a file rather than a property). +variables when the image is actually run. See `docker/Dockerfile` and +`docker/tomcat-conf/` for how that works mechanically (Tomcat's +`EnvironmentPropertySource` for `server.xml`, Spring's `Environment` for +everything else). This doc lists every environment variable the image needs or accepts. It does **not** contain real values for any environment - only what each variable is @@ -25,7 +24,6 @@ docker run -d --name chpl-api \ -e DB_URL="jdbc:postgresql://:5432/openchpl" \ -e DB_USERNAME="" \ -e DB_PASSWORD="" \ - -e JWK_KEY="$(cat /path/to/JSONRsaJoseJWebKey.txt)" \ -e chplUrlBegin="https://chpl-dev.healthit.gov" \ -e SPRING_REDIS_HOST="" \ -e SPRING_REDIS_PORT="6379" \ @@ -49,7 +47,6 @@ property. | `DB_URL` | JDBC URL for the `jdbc/openchpl` datasource (`server.xml`) | | `DB_USERNAME` | DB username (`server.xml`) | | `DB_PASSWORD` | DB password (`server.xml`) | -| `JWK_KEY` | Contents of the RSA JOSE JWK signing key. `entrypoint.sh` writes this to `/usr/local/tomcat/conf/JSONRsaJoseJWebKey.txt` at container start (`keyLocation` is already set in the image - you don't need to set it). | ## Filesystem paths (need a mounted volume, not just an env var) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh deleted file mode 100644 index bc4f691013..0000000000 --- a/docker/entrypoint.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh -# Materializes the JWK signing key from an environment variable into the file -# path chpl-api expects (environment.properties: keyLocation). Everything else -# needed at runtime (DB creds, Cognito/Azure/Jira/Datadog/Redis secrets, etc.) -# is read directly from environment variables by Spring/Tomcat - see -# docker/tomcat-conf/ and chpl/chpl-resources/src/main/resources/environment.properties. -set -e - -if [ -n "$JWK_KEY" ]; then - printf '%s\n' "$JWK_KEY" > /usr/local/tomcat/conf/JSONRsaJoseJWebKey.txt -fi - -exec catalina.sh run diff --git a/docker/tomcat-conf/server.xml b/docker/tomcat-conf/server.xml index 351f32a2c9..9b78900dc6 100644 --- a/docker/tomcat-conf/server.xml +++ b/docker/tomcat-conf/server.xml @@ -52,7 +52,7 @@ pathname="conf/tomcat-users.xml" /> Date: Wed, 22 Jul 2026 23:37:47 -0400 Subject: [PATCH 10/18] ci: add .dockerignore to shrink chpl-api image build context .git (177M) and target/ build dirs (chpl-api/target alone is 1.7GB) were being sent to the Docker daemon as build context on every CI push despite never being referenced by docker/Dockerfile. --- .dockerignore | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..41a127f393 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +.git +**/target/ +node_modules +.vscode +.claude From 7d1ede25fc271626fe5598c02b0cc5babcfea71a Mon Sep 17 00:00:00 2001 From: Todd Young Date: Wed, 22 Jul 2026 23:54:17 -0400 Subject: [PATCH 11/18] chore: remove orphaned .gitattributes rule for deleted entrypoint.sh docker/entrypoint.sh was deleted (its only job, materializing JWK_KEY, was dead code) - the eol=lf rule for it is now orphaned. --- .gitattributes | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.gitattributes b/.gitattributes index 01e4fe34b5..7c64743bf3 100644 --- a/.gitattributes +++ b/.gitattributes @@ -25,10 +25,6 @@ *.xml text *.yml text -# Scripts that execute inside Linux containers must keep LF endings on every -# platform - a CRLF shebang breaks `docker build`/`docker run` on checkout. -docker/entrypoint.sh eol=lf - # These files are binary and should be left untouched # (binary is a macro for -text -diff) *.class binary From 59e779ba3e0bd93d37bc3dd56edc7a1674060824 Mon Sep 17 00:00:00 2001 From: Todd Young Date: Wed, 22 Jul 2026 23:57:14 -0400 Subject: [PATCH 12/18] ci: cancel in-flight docker-publish runs for the same branch The workflow publishes a floating latest- tag; without a concurrency group, two runs for the same branch could race and let an older commit's build finish last, overwriting latest- with a stale image. --- .github/workflows/docker-publish.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index a554c8e50c..7838b8bb60 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -12,6 +12,13 @@ on: - staging - production +# Publishes a floating latest- tag - without this, two runs for the +# same branch could race and let an older commit's build finish last, +# overwriting latest- with a stale image. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + permissions: contents: read packages: write From ea9b5d45a688e9a0a0bc53d1cf6e8c288e487424 Mon Sep 17 00:00:00 2001 From: Todd Young Date: Thu, 23 Jul 2026 10:43:58 -0400 Subject: [PATCH 13/18] ci: run unit tests before building docker image Add a unit-tests job to docker-publish.yml that runs `mvn clean test` on JDK 21, mirroring the Bamboo chpl-build-dev "Run API Unit Tests" job. build-and-push now depends on it via needs, so a broken test suite blocks the image push instead of publishing a broken build. --- .github/workflows/docker-publish.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 7838b8bb60..1fdce0d40b 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -24,7 +24,29 @@ permissions: packages: write jobs: + # Mirrors the "Run API Unit Tests" job in the Bamboo CHPL Development + # Deployment plan (chpl-build/chpl-build-dev): `mvn clean test` against + # chpl/pom.xml on JDK 21. Gates the image build so a broken build never + # gets published. + unit-tests: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + + - name: Run unit tests + env: + MAVEN_OPTS: -Xms512m -Xmx1024m + run: mvn -B clean test --file chpl/pom.xml + build-and-push: + needs: unit-tests runs-on: ubuntu-latest steps: - name: Checkout From 26a13a13c27bc2f95f4b8b99a7081f2d9f09200b Mon Sep 17 00:00:00 2001 From: Todd Young Date: Thu, 23 Jul 2026 11:15:38 -0400 Subject: [PATCH 14/18] ci: fail fast on compile errors before building docker image Add a compile job that runs `mvn clean package -DskipTests`, the same command docker/Dockerfile's build stage runs, mirroring the "Build API"/CompileApiTask job in the chpl-build-dev Bamboo spec. build-and-push now needs both unit-tests and compile, so a broken build fails fast instead of partway through the slower Docker build. --- .github/workflows/docker-publish.yml | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 1fdce0d40b..423cb83dd3 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -45,8 +45,29 @@ jobs: MAVEN_OPTS: -Xms512m -Xmx1024m run: mvn -B clean test --file chpl/pom.xml + # Mirrors the "Build API" job (CompileApiTask) in the same Bamboo plan, and + # runs the exact `mvn clean package -DskipTests` command that docker/Dockerfile's + # build stage runs - so a compile/packaging failure shows up here, fast, + # instead of partway through the (slower) Docker build. + compile: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + + - name: Compile and package + env: + MAVEN_OPTS: -Xms512m -Xmx1024m + run: mvn -B clean package -DskipTests --file chpl/pom.xml + build-and-push: - needs: unit-tests + needs: [unit-tests, compile] runs-on: ubuntu-latest steps: - name: Checkout From cfb9ae66fb39d30fae1e476df642ca143c98d905 Mon Sep 17 00:00:00 2001 From: Todd Young Date: Thu, 23 Jul 2026 11:26:24 -0400 Subject: [PATCH 15/18] test: temporarily break a unit test to verify CI gate Scratch commit for testing docker-publish.yml's unit-tests job - will be reverted. --- .../healthit/chpl/util/CertificationCriterionServiceTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chpl/chpl-service/src/test/java/gov/healthit/chpl/util/CertificationCriterionServiceTest.java b/chpl/chpl-service/src/test/java/gov/healthit/chpl/util/CertificationCriterionServiceTest.java index 8425c93432..00512a1287 100644 --- a/chpl/chpl-service/src/test/java/gov/healthit/chpl/util/CertificationCriterionServiceTest.java +++ b/chpl/chpl-service/src/test/java/gov/healthit/chpl/util/CertificationCriterionServiceTest.java @@ -136,7 +136,7 @@ public void coerceToCriteriaNumber_ValidCriteriaNumber_DoesNotChange() { public void coerceToCriteriaNumber_MissingSpaceBeforeParen_AddsSpace() { String criterionNumber = "170.315(a)(6)"; String result = service.coerceToCriterionNumberFormat(criterionNumber); - assertEquals("170.315 (a)(6)", result); + assertEquals("170.315 (a)(7)", result); // TEMP: intentionally wrong, testing CI gate } @Test From 70ab1942ab05f0f8000295f8f3ad1f4974d583af Mon Sep 17 00:00:00 2001 From: Todd Young Date: Thu, 23 Jul 2026 11:29:04 -0400 Subject: [PATCH 16/18] Revert "test: temporarily break a unit test to verify CI gate" This reverts commit cfb9ae66fb39d30fae1e476df642ca143c98d905. --- .../healthit/chpl/util/CertificationCriterionServiceTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chpl/chpl-service/src/test/java/gov/healthit/chpl/util/CertificationCriterionServiceTest.java b/chpl/chpl-service/src/test/java/gov/healthit/chpl/util/CertificationCriterionServiceTest.java index 00512a1287..8425c93432 100644 --- a/chpl/chpl-service/src/test/java/gov/healthit/chpl/util/CertificationCriterionServiceTest.java +++ b/chpl/chpl-service/src/test/java/gov/healthit/chpl/util/CertificationCriterionServiceTest.java @@ -136,7 +136,7 @@ public void coerceToCriteriaNumber_ValidCriteriaNumber_DoesNotChange() { public void coerceToCriteriaNumber_MissingSpaceBeforeParen_AddsSpace() { String criterionNumber = "170.315(a)(6)"; String result = service.coerceToCriterionNumberFormat(criterionNumber); - assertEquals("170.315 (a)(7)", result); // TEMP: intentionally wrong, testing CI gate + assertEquals("170.315 (a)(6)", result); } @Test From 1d610f9054f7743e99a0659a925ee289247b1543 Mon Sep 17 00:00:00 2001 From: Todd Young Date: Thu, 23 Jul 2026 11:29:41 -0400 Subject: [PATCH 17/18] test: temporarily break compilation to verify CI gate Scratch commit for testing docker-publish.yml's compile job (missing semicolon) - will be reverted. --- .../healthit/chpl/service/CertificationCriterionService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/CertificationCriterionService.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/CertificationCriterionService.java index 80d268f065..18a50f0380 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/CertificationCriterionService.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/CertificationCriterionService.java @@ -275,7 +275,7 @@ public boolean isCriteriaNumber(String input) { } public String coerceToCriterionNumberFormat(String input) { - String formatRegex = "^\\d{3}\\.\\d{3}\\s{1}\\([a-z]{1}\\)(\\([0-9]{1,2}\\))?$"; + String formatRegex = "^\\d{3}\\.\\d{3}\\s{1}\\([a-z]{1}\\)(\\([0-9]{1,2}\\))?$" // TEMP: intentionally broken, testing CI gate if (input.matches(formatRegex)) { LOGGER.debug("\tMatches required format. Not changing input."); return input; From 6892a31a6aeaf65764bf5a0ca5c270f1f9f402fc Mon Sep 17 00:00:00 2001 From: Todd Young Date: Thu, 23 Jul 2026 11:31:11 -0400 Subject: [PATCH 18/18] Revert "test: temporarily break compilation to verify CI gate" This reverts commit 1d610f9054f7743e99a0659a925ee289247b1543. --- .../healthit/chpl/service/CertificationCriterionService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/CertificationCriterionService.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/CertificationCriterionService.java index 18a50f0380..80d268f065 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/CertificationCriterionService.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/CertificationCriterionService.java @@ -275,7 +275,7 @@ public boolean isCriteriaNumber(String input) { } public String coerceToCriterionNumberFormat(String input) { - String formatRegex = "^\\d{3}\\.\\d{3}\\s{1}\\([a-z]{1}\\)(\\([0-9]{1,2}\\))?$" // TEMP: intentionally broken, testing CI gate + String formatRegex = "^\\d{3}\\.\\d{3}\\s{1}\\([a-z]{1}\\)(\\([0-9]{1,2}\\))?$"; if (input.matches(formatRegex)) { LOGGER.debug("\tMatches required format. Not changing input."); return input;