Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions .github/workflows/identity-resolution-helm.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Identity Resolution — Helm contract

# Render-contract gate for the identity-resolution chart, most importantly the
# persons-seed CronJob (#1690): the original bug was the ABSENCE of scheduling,
# so the schedule wiring is contract, not plumbing. Pure `helm template` +
# assertions — no cluster, no images. The functional-k3s lane does not deploy
# identityResolution, so without this nothing would catch a broken CronJob
# template before an instance rollout.

on:
pull_request:
branches: [main]
paths:
- "src/backend/services/identity-resolution/helm/**"
- "charts/insight/**"
- ".github/workflows/identity-resolution-helm.yml"
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
helm-contract:
name: render contract
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
# Pinned to full SHAs (mutable tags are repointable — supply-chain
# hardening, same as semgrep.yml / trivy.yml).
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"

- name: Install test deps
run: pip install --quiet pytest pyyaml

- name: helm lint
run: |
helm lint src/backend/services/identity-resolution/helm
helm dependency update charts/insight
helm lint charts/insight

- name: Render-contract tests (seed CronJob, umbrella tenant guard)
run: python -m pytest src/backend/services/identity-resolution/helm/tests/ -q
3 changes: 3 additions & 0 deletions charts/insight/templates/secrets.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,9 @@ stringData:
{{- end }}

{{- if .Values.identityResolution.deploy }}
{{- if and ((.Values.identityResolution.seed).enabled) (not ((.Values.global | default dict).tenantDefaultId)) (not ((.Values.identityResolution.seed).tenantDefaultId)) }}
{{- fail "identityResolution.seed.enabled=true requires a tenant: set global.tenantDefaultId (it feeds tenant_default_id in insight-identity-resolution-config) or identityResolution.seed.tenantDefaultId — the seed refuses to run without one, so every CronJob run would exit 1. Alternatively disable identityResolution.seed.enabled." }}
{{- end }}
---
# Identity Resolution (Rust port, epic #1602) leaf config. Same gears-rust
# env-override convention as analytics (`APP__gears__<gear>__config__*`; the
Expand Down
10 changes: 10 additions & 0 deletions charts/insight/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,16 @@ identityResolution:
# auto-generated credentials (see templates/secrets.yaml). The subchart
# consumes it via envFrom (overriding its mounted config ConfigMap).
existingSecret: "insight-identity-resolution-config"
# Scheduled persons-seed (#1690): a CronJob runs `identity-resolution seed`
# daily to rebuild the identity org projection (persons / account_person_map
# / org_chart) from ClickHouse identity_inputs — without it the projection
# freezes at the last manual run and Team-view rosters go stale. Runs
# serialize on a per-tenant MariaDB advisory lock; input guards abort on an
# empty identity_inputs read or a tenant mismatch (`--force` overrides).
# Manual run: kubectl create job --from=cronjob/<release>-identity-resolution-seed ...
seed:
enabled: true
schedule: "30 6 * * *" # daily, after overnight syncs + the 06:00 data-quality run
# Gateway-JWT verification (NGINX_BFF R1) — same wiring as analytics: the
# oidc-authn-plugin resolves the authenticator's JWKS via OIDC discovery
# over https on `issuer` and trusts its CA from the authn-tls cert Secret.
Expand Down
1 change: 1 addition & 0 deletions src/backend/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/backend/services/identity-resolution/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ serde_json = { workspace = true }
async-trait = { workspace = true }
axum = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true }

# MariaDB via SeaORM — connection pool + entities + raw resolve queries.
sea-orm = { workspace = true }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
{{- if .Values.seed.enabled }}
# Scheduled persons-seed (#1690): rebuilds the identity org projection
# (`persons` / `account_person_map` / `org_chart`) from ClickHouse
# `identity.identity_inputs` so ingested manager/org changes reach the Team
# view — the projection is materialized and only a seed run refreshes it.
#
# Same image/config/secret wiring as the deployment; the `seed` subcommand
# runs one seed and exits (exit codes: 0 ok / 1 failed / 2 lock busy /
# 3 input guard). Runs serialize on a per-tenant MariaDB advisory lock, so
# `concurrencyPolicy: Forbid` is belt-and-braces for cron-vs-cron only —
# manual Jobs and other Insight instances are serialized by the lock itself.
#
# Manual run:
# kubectl create job --from=cronjob/{{ include "insight-identity-resolution.fullname" . }}-seed seed-manual-$USER
apiVersion: batch/v1
kind: CronJob
metadata:
name: {{ include "insight-identity-resolution.fullname" . }}-seed
labels:
{{- include "insight-identity-resolution.labels" . | nindent 4 }}
app.kubernetes.io/component: persons-seed
spec:
schedule: {{ .Values.seed.schedule | quote }}
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: {{ .Values.seed.successfulJobsHistoryLimit }}
failedJobsHistoryLimit: {{ .Values.seed.failedJobsHistoryLimit }}
jobTemplate:
spec:
# A couple of retries for transient connect blips; the advisory lock +
# the operations journal make a repeated run safe. The deadline caps a
# wedged pod well past the in-binary 10-minute seed timeout.
backoffLimit: 2
activeDeadlineSeconds: 900
template:
metadata:
# NOT the shared selectorLabels: the Service selects on
# name+instance alone, and a seed pod carrying them would enter the
# Service's endpoints (it listens on nothing).
labels:
app.kubernetes.io/name: identity-resolution-seed
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: persons-seed
spec:
{{- with .Values.global }}
{{- with .imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- end }}
enableServiceLinks: false
restartPolicy: Never
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
containers:
- name: persons-seed
image: "{{ required "image.repository is required" .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
command: ["/app/identity-resolution"]
args: ["-c", "/app/config/insight.yaml", "seed"]
volumeMounts:
- name: identity-resolution-config
mountPath: /app/config/insight.yaml
subPath: insight.yaml
readOnly: true
envFrom:
# Same Secret as the deployment: database_url, clickhouse_*,
# tenant_default_id — everything the seed needs.
- secretRef:
name: {{ required "existingSecret is required (umbrella provides `insight-identity-resolution-config`; standalone installs supply their own)" .Values.existingSecret | quote }}
{{- with .Values.seed.tenantDefaultId }}
env:
# Explicit tenant for the seed run — the single source when
# set (in Kubernetes `env` entries override `envFrom`, so
# this wins over any tenant_default_id the Secret carries).
# Standalone installs that don't carry the tenant in their
# Secret set this; the umbrella wires the Secret from
# global.tenantDefaultId instead and validates it at render.
- name: APP__gears__identity-resolution__config__tenant_default_id
value: {{ . | quote }}
{{- end }}
securityContext:
allowPrivilegeEscalation: false
resources:
{{- toYaml .Values.seed.resources | nindent 16 }}
volumes:
- name: identity-resolution-config
configMap:
name: {{ include "insight-identity-resolution.fullname" . }}-gears-config
{{- end }}
Loading
Loading