From cdc57078f84ec58909ea6f7710f1b45a1246d762 Mon Sep 17 00:00:00 2001 From: Todd Kazakov Date: Mon, 11 May 2026 14:42:15 +0300 Subject: [PATCH 1/6] Remove summaries from updated fields in patient CDC events --- .../clinic/templates/5-patients-source-kafka-connector.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/tidepool/charts/clinic/templates/5-patients-source-kafka-connector.yaml b/charts/tidepool/charts/clinic/templates/5-patients-source-kafka-connector.yaml index 6857b9b76..98cf3ea3f 100644 --- a/charts/tidepool/charts/clinic/templates/5-patients-source-kafka-connector.yaml +++ b/charts/tidepool/charts/clinic/templates/5-patients-source-kafka-connector.yaml @@ -14,8 +14,8 @@ spec: collection: patients connection.uri: {{ .Values.global.kafka.connect.mongoConnectionUri }} copy.existing: false - pipeline: '[ {$project: {"fullDocument.summary": 0}} ]' - startup.mode.copy.existing.pipeline: '[ {$project: {"fullDocument.summary": 0}} ]' + pipeline: '[ {$project: {"fullDocument.summary": 0, "updateDescription.updatedFields.summary": 0}} ]' + startup.mode.copy.existing.pipeline: '[ {$project: {"fullDocument.summary": 0, "updateDescription.updatedFields.summary": 0}} ]' database: clinic key.converter: org.apache.kafka.connect.json.JsonConverter key.converter.schemas.enable: false From 6a4bf9ba8a991f79a82a69ae2e56d948300a3309 Mon Sep 17 00:00:00 2001 From: Todd Kazakov Date: Wed, 10 Jun 2026 11:50:34 +0300 Subject: [PATCH 2/6] Add user activity table --- .../templates/2-users-source-kafka-connector.yaml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/charts/tidepool/charts/kafka/templates/2-users-source-kafka-connector.yaml b/charts/tidepool/charts/kafka/templates/2-users-source-kafka-connector.yaml index 85717257c..33015bcff 100644 --- a/charts/tidepool/charts/kafka/templates/2-users-source-kafka-connector.yaml +++ b/charts/tidepool/charts/kafka/templates/2-users-source-kafka-connector.yaml @@ -27,11 +27,17 @@ spec: value.converter: 'org.apache.kafka.connect.json.JsonConverter' value.converter.schemas.enable: false snapshot.mode: {{ .Values.keycloak.snapshotMode }} - table.include.list: 'public.user_entity,public.user_role_mapping,public.keycloak_role,public.user_attribute' - transforms: 'filter,filter_user_attr' + table.include.list: 'public.user_entity,public.user_role_mapping,public.keycloak_role,public.user_attribute,public.tidepool_user_activity_event' + # Key the user-activity outbox by user_id (rather than the row's PK) so every + # event for a user lands on one partition and is consumed in commit order. This + # is what lets the clinic-worker consumer apply updates as last-writer-wins + # safely; combined with snapshot.mode=never it removes out-of-order/replay + # regressions. Other tables keep their default primary-key based message key. + message.key.columns: 'public.tidepool_user_activity_event:user_id' + transforms: 'filter' transforms.filter.type: 'io.debezium.transforms.Filter' transforms.filter.language: 'jsr223.groovy' - transforms.filter.topic.regex: '.+\.public.(user_entity|keycloak_role)' + transforms.filter.topic.regex: '.+\.public.(user_entity|keycloak_role|tidepool_user_activity_event)' transforms.filter.condition: "value.op && (((value.op == 'r' || value.op == 'c' || value.op == 'u') && (value.after && value.after.realm_id && value.after.realm_id == '{{ $realm }}')) || (value.op == 'd'))" transforms.filter_user_attr.type: 'io.debezium.transforms.Filter' transforms.filter_user_attr.language: 'jsr223.groovy' From 82350e651f1d0ca7ec57f94e34ff15d7653a2905 Mon Sep 17 00:00:00 2001 From: Todd Kazakov Date: Wed, 10 Jun 2026 12:03:17 +0300 Subject: [PATCH 3/6] Add user activity events --- .../templates/2-users-source-kafka-connector.yaml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/charts/tidepool/charts/kafka/templates/2-users-source-kafka-connector.yaml b/charts/tidepool/charts/kafka/templates/2-users-source-kafka-connector.yaml index 33015bcff..e889efc06 100644 --- a/charts/tidepool/charts/kafka/templates/2-users-source-kafka-connector.yaml +++ b/charts/tidepool/charts/kafka/templates/2-users-source-kafka-connector.yaml @@ -34,11 +34,20 @@ spec: # safely; combined with snapshot.mode=never it removes out-of-order/replay # regressions. Other tables keep their default primary-key based message key. message.key.columns: 'public.tidepool_user_activity_event:user_id' - transforms: 'filter' + transforms: 'filter,user_activity_filter,filter_user_attr' transforms.filter.type: 'io.debezium.transforms.Filter' transforms.filter.language: 'jsr223.groovy' - transforms.filter.topic.regex: '.+\.public.(user_entity|keycloak_role|tidepool_user_activity_event)' + transforms.filter.topic.regex: '.+\.public.(user_entity|keycloak_role)' transforms.filter.condition: "value.op && (((value.op == 'r' || value.op == 'c' || value.op == 'u') && (value.after && value.after.realm_id && value.after.realm_id == '{{ $realm }}')) || (value.op == 'd'))" + # Realm filter scoped to the user-activity outbox only. Keeps inserts (and any + # snapshot reads/updates) for the configured realm and drops everything else, + # including pruning deletes (their `after` is null, so they fail the check) — + # the clinic-worker consumer ignores deletes anyway. Records on other topics do + # not match this regex and pass through untouched. + transforms.user_activity_filter.type: 'io.debezium.transforms.Filter' + transforms.user_activity_filter.language: 'jsr223.groovy' + transforms.user_activity_filter.topic.regex: '.+\.public.tidepool_user_activity_event' + transforms.user_activity_filter.condition: "value.op && (value.op == 'r' || value.op == 'c' || value.op == 'u') && value.after && value.after.realm_id && value.after.realm_id == '{{ $realm }}'" transforms.filter_user_attr.type: 'io.debezium.transforms.Filter' transforms.filter_user_attr.language: 'jsr223.groovy' transforms.filter_user_attr.topic.regex: '.+\.public.(user_attribute)' From 2ceeef3115cdb8e9fa7198d6050a1eac7b6b1635 Mon Sep 17 00:00:00 2001 From: Todd Kazakov Date: Thu, 11 Jun 2026 17:22:05 +0300 Subject: [PATCH 4/6] Drop user-activity tombstones in connector filter Set null.handling.mode=drop on user_activity_filter so pruning-delete tombstones are discarded at the source instead of reaching the consumer. Scoped to this filter's topic.regex, so keycloak tables are unaffected. --- .../kafka/templates/2-users-source-kafka-connector.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/charts/tidepool/charts/kafka/templates/2-users-source-kafka-connector.yaml b/charts/tidepool/charts/kafka/templates/2-users-source-kafka-connector.yaml index e889efc06..016741ec9 100644 --- a/charts/tidepool/charts/kafka/templates/2-users-source-kafka-connector.yaml +++ b/charts/tidepool/charts/kafka/templates/2-users-source-kafka-connector.yaml @@ -47,6 +47,10 @@ spec: transforms.user_activity_filter.type: 'io.debezium.transforms.Filter' transforms.user_activity_filter.language: 'jsr223.groovy' transforms.user_activity_filter.topic.regex: '.+\.public.tidepool_user_activity_event' + # Drop pruning-delete tombstones (null value) for this topic instead of the + # default 'keep', so they don't reach the consumer. Scoped to this filter's + # topic.regex, so the keycloak tables' tombstones are unaffected. + transforms.user_activity_filter.null.handling.mode: 'drop' transforms.user_activity_filter.condition: "value.op && (value.op == 'r' || value.op == 'c' || value.op == 'u') && value.after && value.after.realm_id && value.after.realm_id == '{{ $realm }}'" transforms.filter_user_attr.type: 'io.debezium.transforms.Filter' transforms.filter_user_attr.language: 'jsr223.groovy' From f1346de8fc75de8df046755d45e64ced12208b86 Mon Sep 17 00:00:00 2001 From: Todd Kazakov Date: Fri, 14 Aug 2026 16:38:52 +0300 Subject: [PATCH 5/6] Allow specifying custom debezium slot name --- .../charts/kafka/templates/2-users-source-kafka-connector.yaml | 1 + charts/tidepool/values.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/charts/tidepool/charts/kafka/templates/2-users-source-kafka-connector.yaml b/charts/tidepool/charts/kafka/templates/2-users-source-kafka-connector.yaml index 016741ec9..dbca38a9d 100644 --- a/charts/tidepool/charts/kafka/templates/2-users-source-kafka-connector.yaml +++ b/charts/tidepool/charts/kafka/templates/2-users-source-kafka-connector.yaml @@ -27,6 +27,7 @@ spec: value.converter: 'org.apache.kafka.connect.json.JsonConverter' value.converter.schemas.enable: false snapshot.mode: {{ .Values.keycloak.snapshotMode }} + slot.name: {{ .Values.global.kafka.connect.keycloak.slotName }} table.include.list: 'public.user_entity,public.user_role_mapping,public.keycloak_role,public.user_attribute,public.tidepool_user_activity_event' # Key the user-activity outbox by user_id (rather than the row's PK) so every # event for a user lands on one partition and is consumed in commit order. This diff --git a/charts/tidepool/values.yaml b/charts/tidepool/values.yaml index 43f9c7c4e..68f6a00df 100644 --- a/charts/tidepool/values.yaml +++ b/charts/tidepool/values.yaml @@ -51,6 +51,7 @@ global: # -- enable keycloak kafka connector keycloak: enabled: false + slotName: debezium # -- The password key ref passwordKeyName: "Password" linkerdsupport: From 6bee4981901da71367f918582ffe7a730b991c3f Mon Sep 17 00:00:00 2001 From: Todd Kazakov Date: Wed, 9 Sep 2026 18:57:52 +0300 Subject: [PATCH 6/6] Add Tandem OAuth provider configuration --- .../charts/auth/templates/1-deployment.yaml | 58 ++++++++++++++++- .../charts/data/templates/1-deployment.yaml | 64 ++++++++++++++++++- charts/tidepool/charts/tandem/.helmignore | 22 +++++++ charts/tidepool/charts/tandem/Chart.yaml | 6 ++ charts/tidepool/charts/tandem/README.md | 27 ++++++++ .../charts/tandem/templates/0-configmap.yaml | 21 ++++++ .../charts/tandem/templates/0-secret.yaml | 15 +++++ charts/tidepool/charts/tandem/values.yaml | 15 +++++ local/README.md | 10 ++- 9 files changed, 231 insertions(+), 7 deletions(-) create mode 100644 charts/tidepool/charts/tandem/.helmignore create mode 100644 charts/tidepool/charts/tandem/Chart.yaml create mode 100644 charts/tidepool/charts/tandem/README.md create mode 100644 charts/tidepool/charts/tandem/templates/0-configmap.yaml create mode 100644 charts/tidepool/charts/tandem/templates/0-secret.yaml create mode 100644 charts/tidepool/charts/tandem/values.yaml diff --git a/charts/tidepool/charts/auth/templates/1-deployment.yaml b/charts/tidepool/charts/auth/templates/1-deployment.yaml index 3f2835ef1..dcfffcce7 100644 --- a/charts/tidepool/charts/auth/templates/1-deployment.yaml +++ b/charts/tidepool/charts/auth/templates/1-deployment.yaml @@ -8,8 +8,8 @@ metadata: name: auth namespace: {{.Release.Namespace}} annotations: - secret.reloader.stakater.com/reload: "auth,server,{{ .Values.mongo.secretName }},abbott,customer-io,dexcom,oura,twiist" - configmap.reloader.stakater.com/reload: "abbott,auth,customer-io,dexcom,oura,twiist" + secret.reloader.stakater.com/reload: "auth,server,{{ .Values.mongo.secretName }},abbott,customer-io,dexcom,oura,tandem,twiist" + configmap.reloader.stakater.com/reload: "abbott,auth,customer-io,dexcom,oura,tandem,twiist" {{ if .Values.deployment.annotations }} {{- .Values.deployment.annotations | toYaml | nindent 4 }} {{- end }} @@ -105,6 +105,60 @@ spec: name: abbott key: StateSalt optional: true + - name: TIDEPOOL_SERVICE_PROVIDER_TANDEM_AUTHORIZE_URL + valueFrom: + configMapKeyRef: + name: tandem + key: AuthorizeURL + optional: true + - name: TIDEPOOL_SERVICE_PROVIDER_TANDEM_REDIRECT_URL + valueFrom: + configMapKeyRef: + name: tandem + key: RedirectURL + optional: true + - name: TIDEPOOL_SERVICE_PROVIDER_TANDEM_SCOPES + valueFrom: + configMapKeyRef: + name: tandem + key: Scopes + optional: true + - name: TIDEPOOL_SERVICE_PROVIDER_TANDEM_TOKEN_URL + valueFrom: + configMapKeyRef: + name: tandem + key: TokenURL + optional: true + - name: TIDEPOOL_SERVICE_PROVIDER_TANDEM_REVOKE_URL + valueFrom: + configMapKeyRef: + name: tandem + key: RevokeURL + optional: true + - name: TIDEPOOL_SERVICE_PROVIDER_TANDEM_JWKS_URL + valueFrom: + configMapKeyRef: + name: tandem + key: JWKSURL + optional: true + - name: TIDEPOOL_SERVICE_PROVIDER_TANDEM_CLIENT_ID + valueFrom: + secretKeyRef: + name: tandem + key: ClientId + optional: true + - name: TIDEPOOL_SERVICE_PROVIDER_TANDEM_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: tandem + key: ClientSecret + optional: true + - name: TIDEPOOL_SERVICE_PROVIDER_TANDEM_STATE_SALT + valueFrom: + secretKeyRef: + name: tandem + key: StateSalt + optional: true - name: TIDEPOOL_SERVICE_PROVIDER_DEXCOM_AUTHORIZE_URL valueFrom: configMapKeyRef: diff --git a/charts/tidepool/charts/data/templates/1-deployment.yaml b/charts/tidepool/charts/data/templates/1-deployment.yaml index c0ddd71bc..9ed10f867 100644 --- a/charts/tidepool/charts/data/templates/1-deployment.yaml +++ b/charts/tidepool/charts/data/templates/1-deployment.yaml @@ -8,8 +8,8 @@ metadata: name: data namespace: {{.Release.Namespace}} annotations: - secret.reloader.stakater.com/reload: "server,{{ .Values.mongo.secretName }},abbott,oura,twiist,data" - configmap.reloader.stakater.com/reload: "abbott,oura,twiist" + secret.reloader.stakater.com/reload: "server,{{ .Values.mongo.secretName }},abbott,oura,tandem,twiist,data" + configmap.reloader.stakater.com/reload: "abbott,oura,tandem,twiist" {{ if .Values.deployment.annotations }} {{- .Values.deployment.annotations | toYaml | nindent 4 }} {{- end }} @@ -130,6 +130,66 @@ spec: name: abbott key: PartnerURL optional: true + - name: TIDEPOOL_SERVICE_PROVIDER_TANDEM_AUTHORIZE_URL + valueFrom: + configMapKeyRef: + name: tandem + key: AuthorizeURL + optional: true + - name: TIDEPOOL_SERVICE_PROVIDER_TANDEM_REDIRECT_URL + valueFrom: + configMapKeyRef: + name: tandem + key: RedirectURL + optional: true + - name: TIDEPOOL_SERVICE_PROVIDER_TANDEM_SCOPES + valueFrom: + configMapKeyRef: + name: tandem + key: Scopes + optional: true + - name: TIDEPOOL_SERVICE_PROVIDER_TANDEM_TOKEN_URL + valueFrom: + configMapKeyRef: + name: tandem + key: TokenURL + optional: true + - name: TIDEPOOL_SERVICE_PROVIDER_TANDEM_REVOKE_URL + valueFrom: + configMapKeyRef: + name: tandem + key: RevokeURL + optional: true + - name: TIDEPOOL_SERVICE_PROVIDER_TANDEM_JWKS_URL + valueFrom: + configMapKeyRef: + name: tandem + key: JWKSURL + optional: true + - name: TIDEPOOL_SERVICE_PROVIDER_TANDEM_CLIENT_ID + valueFrom: + secretKeyRef: + name: tandem + key: ClientId + optional: true + - name: TIDEPOOL_SERVICE_PROVIDER_TANDEM_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: tandem + key: ClientSecret + optional: true + - name: TIDEPOOL_SERVICE_PROVIDER_TANDEM_STATE_SALT + valueFrom: + secretKeyRef: + name: tandem + key: StateSalt + optional: true + - name: TIDEPOOL_TANDEM_CLIENT_ADDRESS + valueFrom: + configMapKeyRef: + name: tandem + key: ClientURL + optional: true - name: TIDEPOOL_SERVICE_PROVIDER_OURA_CLIENT_ID valueFrom: secretKeyRef: diff --git a/charts/tidepool/charts/tandem/.helmignore b/charts/tidepool/charts/tandem/.helmignore new file mode 100644 index 000000000..50af03172 --- /dev/null +++ b/charts/tidepool/charts/tandem/.helmignore @@ -0,0 +1,22 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/charts/tidepool/charts/tandem/Chart.yaml b/charts/tidepool/charts/tandem/Chart.yaml new file mode 100644 index 000000000..20248e5df --- /dev/null +++ b/charts/tidepool/charts/tandem/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +appVersion: "1.0" +description: A Helm chart for Kubernetes +name: tandem +version: 0.1.0 +home: https://github.com/tidepool-org/development/charts diff --git a/charts/tidepool/charts/tandem/README.md b/charts/tidepool/charts/tandem/README.md new file mode 100644 index 000000000..b0064ffb4 --- /dev/null +++ b/charts/tidepool/charts/tandem/README.md @@ -0,0 +1,27 @@ +# tandem + +![Version: 0.1.0](https://img.shields.io/badge/Version-0.1.0-informational?style=flat-square) ![AppVersion: 1.0](https://img.shields.io/badge/AppVersion-1.0-informational?style=flat-square) + +A Helm chart for Kubernetes + +**Homepage:** + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| configmap.enabled | bool | `true` | whether to generate a configmap | +| configmap.redirectURL | string | `""` | OAuth2 redirect URL | +| configmap.tokenURL | string | `""` | OAuth2 token URL | +| configmap.authorizeURL | string | `""` | OAuth2 authorization URL | +| configmap.revokeURL | string | `""` | OAuth2 revoke URL | +| configmap.jwksURL | string | `""` | JWKS URL | +| configmap.clientURL | string | `""` | Tandem API client URL | +| configmap.scopes | string | `""` | OAuth2 scopes | +| secret.enabled | bool | `false` | whether to create a secret | +| secret.data_.ClientId | string | `""` | plaintext OAuth2 client id | +| secret.data_.ClientSecret | string | `""` | plaintext OAuth2 client secret | +| secret.data_.StateSalt | string | `""` | plaintext OAuth2 state salt (also keys the PKCE code verifier) | + +---------------------------------------------- +Autogenerated from chart metadata using [helm-docs v1.3.0](https://github.com/norwoodj/helm-docs/releases/v1.3.0) diff --git a/charts/tidepool/charts/tandem/templates/0-configmap.yaml b/charts/tidepool/charts/tandem/templates/0-configmap.yaml new file mode 100644 index 000000000..29f2e59fd --- /dev/null +++ b/charts/tidepool/charts/tandem/templates/0-configmap.yaml @@ -0,0 +1,21 @@ +{{ if .Values.configmap.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: tandem + namespace: {{ .Release.Namespace }} + labels: +{{ include "charts.labels.standard" . }} +data: +{{ if .Values.configmap.redirectURL }} + RedirectURL: {{ .Values.configmap.redirectURL }} +{{ else }} + RedirectURL: "{{include "charts.host.api" .}}/v1/oauth/tandem/redirect" +{{ end }} + TokenURL: {{ .Values.configmap.tokenURL | default "" }} + AuthorizeURL: {{ .Values.configmap.authorizeURL | default "" }} + RevokeURL: {{ .Values.configmap.revokeURL | default "" }} + JWKSURL: {{ .Values.configmap.jwksURL | default "" }} + ClientURL: {{ .Values.configmap.clientURL | default "" }} + Scopes: {{ .Values.configmap.scopes | default "" }} +{{ end }} diff --git a/charts/tidepool/charts/tandem/templates/0-secret.yaml b/charts/tidepool/charts/tandem/templates/0-secret.yaml new file mode 100644 index 000000000..eadceadb9 --- /dev/null +++ b/charts/tidepool/charts/tandem/templates/0-secret.yaml @@ -0,0 +1,15 @@ +{{ if .Values.secret.enabled -}} +--- +apiVersion: v1 +{{ with .Values.secret.data_ -}} +data: + ClientId: {{ .ClientId | default "" | b64enc | quote }} + ClientSecret: {{ .ClientSecret | default "" | b64enc | quote }} + StateSalt: {{ .StateSalt | default "" | b64enc | quote }} +{{- end }} +kind: Secret +metadata: + name: tandem + namespace: {{ .Release.Namespace }} +type: Opaque +{{- end }} diff --git a/charts/tidepool/charts/tandem/values.yaml b/charts/tidepool/charts/tandem/values.yaml new file mode 100644 index 000000000..685ede78b --- /dev/null +++ b/charts/tidepool/charts/tandem/values.yaml @@ -0,0 +1,15 @@ +configmap: + enabled: true + redirectURL: "" + tokenURL: "" + authorizeURL: "" + revokeURL: "" + jwksURL: "" + clientURL: "" + scopes: "" +secret: + enabled: false + data_: + ClientId: "" + ClientSecret: "" + StateSalt: "" diff --git a/local/README.md b/local/README.md index 0b2ee1a84..5aaced557 100644 --- a/local/README.md +++ b/local/README.md @@ -61,9 +61,9 @@ prescription: - name: registry ``` -# OAuth Provider Configuration (Abbott/Dexcom) +# OAuth Provider Configuration (Abbott/Dexcom/Tandem) -To allow Tilt to use the Abbott and Dexcom OAuth providers it is necessary to enable the related Secret and +To allow Tilt to use the Abbott, Dexcom and Tandem OAuth providers it is necessary to enable the related Secret and ConfigMap. To do so, add the following to your `local/Tiltconfig.yaml`, once for each provider you wish to enable: ``` @@ -83,7 +83,7 @@ ConfigMap. To do so, add the following to your `local/Tiltconfig.yaml`, once for StateSalt: "" ``` -The top-level `` should be replaced with `dexcom` or `abbott`, as appropriate. The other property values should +The top-level `` should be replaced with `dexcom`, `abbott` or `tandem`, as appropriate. The other property values should be changed to use the provider-specific settings. Multiple providers may be specified, if so desired. ## Provider-Specific Settings @@ -105,3 +105,7 @@ Use either: Abbott settings can be found attached to the `Abbott Developer` item in the `Engineering` vault in 1Password. Use the `local.yaml` for connecting to the Abbott Sandbox environment. + +## Tandem + +Tandem requires the OAuth2 Authorization Code flow with PKCE, which the platform enables automatically for this provider. The `redirectURL` must be registered with Tandem for each environment. The `clientURL` is required by the `data` service once the provider is configured.