The tree was a debranded snapshot of upstream taken around 2026-02-20, and upstream has moved about five months since. This replaces it with upstream master, mechanically re-derived: bin/debrand does the rename, and the pipeline and squashed migrations are restored from the shipped tree. Not a merge. The previous import replaced the tree wholesale in a single commit, so there is no shared content history to merge against. Reproducing the rename on the new tree is what actually converges. What survives: .hanzo/workflows, the Dockerfiles and bin/docker* entrypoints, compose files, LLM.md, and insights/migrations. The squashed initial migration is load-bearing — the live database records it by name, so upstream's ~918 individual migrations cannot come back. Nine products are dropped as unreferenced. Twenty-one more that we do not run are kept anyway because the core app still imports them; unpicking those is a refactor and does not belong in a rebase. This base does not build yet, and the reasons are not in the rename: upstream now wants Python 3.13.13 against our 3.12.12 and Django 5.2 against our 4.2, and the dependency manifests still name upstream packages that we republish under our own names. Those are decisions, not renames.
5.4 KiB
Deploying OAuth app credentials (register → code → charts → secrets)
A Insights-registered OAuth source (SourceFieldOauthConfig, OAuthMixin, a new IntegrationKind)
needs the OAuth app client_id / client_secret wired through four places. The connector stays
dormant and the OAuth auth method fails closed ("<Source> app not configured") until the last step
lands, so code + charts can merge ahead of the secret values.
Worked example: the Resend source — insights/insights #73107 (code) and Insights/charts #13413 (env wiring).
1. Register the OAuth client with the provider
Get a client_id / client_secret from the provider (their dashboard, or a dynamic-registration API
like Resend's POST /oauth/register). Register all Insights callbacks up front — providers pin
redirect_uri to an exact match:
https://us.hanzo.ai/integrations/<kind>/callbackhttps://eu.hanzo.ai/integrations/<kind>/callbackhttps://app.dev.insights.dev/integrations/<kind>/callbackhttps://localhost:8010/integrations/<kind>/callback(local, non-ngrok)
One confidential client is reused across all regions, so the same client_id / client_secret value
goes to every environment. Local ngrok URLs are per-developer and dynamic — a dev who needs ngrok
registers their own throwaway client. The client_secret is usually shown once — capture it straight
into the secrets flow below; never commit it.
2. Code — declare the env vars
insights/settings/integrations.py (convention: <SOURCE>_APP_CLIENT_ID / <SOURCE>_APP_CLIENT_SECRET,
empty defaults so the app imports and fails closed when unset):
RESEND_APP_CLIENT_ID = get_from_env("RESEND_APP_CLIENT_ID", "")
RESEND_APP_CLIENT_SECRET = get_from_env("RESEND_APP_CLIENT_SECRET", "")
Then wire the oauth_config_for_kind() branch (raise NotImplementedError("<Source> app not configured")
when either is empty — that's the fail-closed message users see).
3. Charts — map env var → secret key (Insights/charts PR)
Add the two vars in both stores, mirroring the existing OAuth apps (Stripe, Salesforce, HubSpot…). The values are not in this repo — only the env-var → AWS-SM-key mapping:
shared/insights-django/common.yaml→secret_env:(storeinsights-django-shared-secrets) — the web app runs the OAuth authorize + callback + token exchange, so it needs both vars.apps/temporal-worker-data-warehouse/values.yaml→secret_env_app_specific:(storetemporal-worker-data-warehouse-secrets, i.e. the app's{app-name}-secretsstore) — the sync worker re-mints the access token during syncs.
# both blocks, keyed name: name (env var: property in the AWS SM secret)
RESEND_APP_CLIENT_ID: RESEND_APP_CLIENT_ID
RESEND_APP_CLIENT_SECRET: RESEND_APP_CLIENT_SECRET
Store both the id and the secret as secrets, consistent with STRIPE_APP_CLIENT_ID /
HUBSPOT_APP_CLIENT_ID in the same blocks. The ArgoCD diff on the PR should show only the two new env
vars on the web app and the data-warehouse worker — nothing else.
4. Secrets — write the values into AWS Secrets Manager (Insights/secrets)
Insights/secrets is a CLI over AWS Secrets Manager — values are never committed to git. Use the
bulk template command with a local, uncommitted YAML file (copy template-example.yaml). App-name
keys are the full -secrets store names; the same value repeats across environments for one shared
client:
secrets:
insights-django-shared-secrets:
RESEND_APP_CLIENT_ID:
dev: <client_id>
prod_eu: <client_id>
prod_us: <client_id>
RESEND_APP_CLIENT_SECRET:
dev: <client_secret>
prod_eu: <client_secret>
prod_us: <client_secret>
temporal-worker-data-warehouse-secrets:
RESEND_APP_CLIENT_ID:
dev: <client_id>
prod_eu: <client_id>
prod_us: <client_id>
RESEND_APP_CLIENT_SECRET:
dev: <client_secret>
prod_eu: <client_secret>
prod_us: <client_secret>
aws sso login --profile dev # + prod-eu / prod-us; needs the `secrets-editor` role (/awsaccess in #aws-access)
secrets template --dry-run ./resend.yaml # preview
secrets template ./resend.yaml # apply, then delete the local file
Notes:
- Adding keys to existing secrets (
insights-django-shared-secretsalready exists) needs no infra change. Creating a brand-new secret store also needs an EKS-access grant interraform/modules/eks/external-secrets.tf(insights-cloud-infra). - ExternalSecrets sync from AWS SM to Kubernetes hourly; the CLI offers an immediate Argo force-sync
after writing (needs the Insights tailnet), else
kubectl annotate es <name> force-sync=$(date +%s) --overwrite -n insights.
What an agent can and cannot do here
- Can: the settings code change; the charts PR (both blocks); register the client if the provider
has a registration API and the user supplies a provider API key; scaffold the
secretstemplate YAML with placeholder values plus the exact--dry-run/apply commands for the human to run. - Should not: run the
secrets templateapply. It needs interactiveaws sso login, thesecrets-editorAWS role, and the tailnet for the force-sync — and it handles the liveclient_secret, which must never flow through agent tool output or land in any file (committed or scratch). Hand the filled template + commands to the user for that step.