# CLAUDE.md Guidance for Claude Code (claude.ai/code) in this repository. ## ⚠️ CRITICAL — LIFE OR DEATH: zero code comments **This is the most important rule in this file. Violating it is treated as a failed task.** Never write a comment that narrates, restates, or explains what the code already says — no "fetch the user", no "call the service", no explaining your change to the reviewer. Make the code self-explanatory instead: better names, smaller functions. ```ts // BAD — restates the code. Never do this: // fetch the user's repositories const repos = await this.repositoryService.list(userId); // GOOD — no comment; the code already says it: const repos = await this.repositoryService.list(userId); ``` The only permitted comment captures something the code *cannot* express — a why, a constraint, a gotcha a reader would otherwise miss. These are rare. When in doubt, do not comment. **Before finishing any edit, re-read your diff and delete every comment that fails this bar.** This rule is about code. Infra config (`tf/`, `ansible/`, `kubernetes/`) has no equivalent remedy: the keys belong to Ceph, Terraform and Ansible, so there is no better name to pick and no smaller function to extract. The bar is the same and restating a value is still banned, but a comment recording why a value deviates from its default, or what breaks when it changes, is usually the only place that knowledge can live. ## What this is Yucca is a multi-tenant **backup service**: OIDC-authenticated users get S3-backed [restic](https://restic.net/) repositories. Two things at once: - **Application** (`packages/`) — NestJS/Go/Svelte services. - **Infrastructure** (`tf/`, `ansible/`, `kubernetes/`, `charts/`) — Ceph object storage, Talos K8s, Flux GitOps, networking. See `README.md` and per-directory `README.md` files. ## Tooling - Everything runs through **[mise](https://mise.jdx.dev/)** tasks (`.mise/tasks/` scripts + `.mise/config.toml` aggregates). mise pins every binary (node, pnpm, go, kubectl, helm, tilt, opentofu, ansible…) — do not assume a tool is on PATH outside of mise. - **pnpm workspaces** with a **catalog** (`pnpm-workspace.yaml`): add/bump shared deps in the catalog, referenced as `"catalog:"` in each `package.json` — not in individual packages. - Secrets come from **1Password** via `op run`; `.env` files contain `op://` refs, never literal secrets. `OP_ACCOUNT` is set in `.env` (copy `.env.example`). ## Common commands ```bash mise dev # compose-based dev: deps, docker infra (postgres/minio/mock-oidc/victoria-*), all *:dev mise :dev # one service, e.g. mise web:dev, mise yucca-api:dev mise docs:dev # docs site (packages/docs) on :36034 mise check # lint + format check + svelte-check + unit tests (= the `checks` CI job) mise fix # autofix lint/format + lingui extract mise build # build all packages mise test # all unit tests (jest per NestJS pkg, vitest for web) mise test:integration # integration tests (--jobs 1; needs infra up) mise test:integration:k3d # CI split: the database-backed suites; :s3 = the Ceph-backed ones mise test:e2e # e2e (needs the stack running); mise test:e2e:web = Playwright mise :test # one package; args after -- go to jest: mise yucca-api:test -- -t "name" mise yucca-api:migrations # DB migrations (@immich/sql-tools; yucca-api owns the schema) ``` ### k3d + Tilt (prod-shaped dev) `mise k3d:up` → `mise tilt:up` (build images, render charts from `kubernetes/apps/dev/local`, port-forward, live_update); `tilt:down` / `k3d:down` to tear down. Tilt's source of truth is the Flux tree under `kubernetes/` — see the extensively commented `Tiltfile`. CI deploys resource subsets rather than the whole stack (`mise tilt:ci` still does everything): `tilt:ci-infra` (integration — postgres, mock-oidc, victoria; **no Ceph**), then for e2e `tilt:ci-ceph` (Rook only; builds no images, so it converges while the workspace installs) followed by `tilt:ci-e2e` (the apps the e2e suites touch). Ceph converging outweighed every test in the integration job, so that job dropped it and its S3-backed suites moved to the e2e one. Ports: `5173` web · `3020` yucca-api · `3030` yucca-admin-api · `3010` michael · `8092` mock-oidc · `8025` mailpit · `8093` mock-postmark · `9000` ceph rgw · `8428` victoria-metrics · `9428` victoria-logs. ### Infrastructure commands ```bash mise tf:plan / tf:init / tf:fmt # terragrunt (TF_STACK_DIR=tf/deployment///) mise k8s:validate # helm template + kubeconform + flux-local of the k8s tree (no cluster) mise mgmt:render-inventory / mgmt:ansible ``` **CI owns terraform applies** (`.github/workflows/infra.yml` on merge to main). Locally you may `tf:plan` but never `tf:apply`. Gotcha: unset the shell's stray `AWS_CA_BUNDLE` before planning — it breaks the OVH S3 state backend. We own the `futo-org/netbird` provider (`../terraform-provider-netbird`, pin ≥ 1.0.2); renaming a NetBird setup key forces replacement (the API can't rename keys), regenerating its value. ## Application architecture Backend services are **NestJS 11 + TypeScript**: controllers → services → repositories, Zod-validated `env.ts`, JWT auth guards via `@AuthRoute()`, OTel from `@common/server` (imported at bootstrap; pino logs, OTLP to victoria-*). | Service | Lang | Role | |---|---|---| | `yucca-api` | NestJS | User-facing API. Owns auth (OIDC code + device flow, ES256 JWTs), repositories, **DB schema + migrations**. | | `yucca-admin-api` | NestJS | Admin API (user/session/repository management). Same DB + JWT validation. | | `michael` | Go | **Production** restic REST backend — S3 proxy with JWT verification, WORM enforcement, backend pooling. | | `columbo` | Go | Ticket investigation agent: LLM loop (OpenRouter) over per-user-scoped o11y queries, answers only into staff threads. See `docs/columbo.md`. | | `monk` | Go | Ceph scrub-backlog exporter: polls `pg ls`, serves measured per-pool scrub-age metrics on :9284. Deployed onto mon hosts by ansible, not K8s. | | `yucca-metrics-worker` | NestJS | 5-min cron: RadosGW usage → meter tables → per-connection rollup (`connectionMetrics`, billing floor), OTel gauges. | | `redis` (valkey) | | Shared platform cache (ephemeral; keys `yucca:::*`). Primary-region only. | | `mock-oidc-provider` | Node | Dev/test OIDC IdP (code + device flow). | | `mock-postmark-provider` | Node | Dev/test Postmark API mock; delivers into the Mailpit inbox. | | `common` (`@common/server`) | TS lib | OTel init, pino repository, **feature-flag registry + connection-type registry**, Postmark `EmailRepository` (`@common/server/email`). | | `emails` (`@common/emails`) | Svelte lib | Transactional email templates (better-svelte-email, web theme), prebuilt to JS for the NestJS apps. See `docs/email.md`. | **Frontend** (`packages/web`): SvelteKit 5 + Tailwind 4, `@immich/ui`, lingui i18n (`mise web:lingui:*`; compiled locales are generated, not edited), generated API client. **Docs** (`packages/docs`, https://docs.futo.cloud): the **end-user** documentation site (beta setup guides for Immich and the standalone container). SvelteKit + `adapter-static`; every page is a `src/routes/
//+page.md` compiled by `@immich/svelte-markdown-preprocess` (front matter `title`/`description`/`order`; sections in `src/lib/index.ts`). Deployed to Cloudflare Pages (`tf/pages/docs` + `.github/workflows/docs.yml`; every PR gets a preview). Internal/developer docs stay in `docs/` and the per-directory READMEs — do not move them onto the site. **`packages/yucca-sdk/`** (orchestration-api + orchestration-ui) is separately versioned and added explicitly in `pnpm-workspace.yaml`. **API client generation**: yucca-api DTOs → `mise yucca-api:sync-openapi` → `mise yucca-api-client:build` (oazapfts) → `packages/yucca-api-client/src/fetch-client.ts`. Generated — regenerate on contract changes, never hand-edit. **Database**: PostgreSQL via **Kysely**. Schema in `packages/yucca-api/src/schema/` (`tables/`, `migrations/`); yucca-api is the schema owner, other services read the same DB. **Go services**: `michael` and `yuctl` are Go 1.25, `internal/`, aws-sdk-go-v2, zerolog. `yuctl` (cobra) is the ops CLI: reads the Terraform **discovery** contract from S3 state to resolve topology and drive day-2 ops. See `packages/yuctl/README.md`. ### Connections, feature flags, token revocation Full detail: `docs/connections.md`. The essentials: - **Connections**: a user has N connection instances of type `immich`/`restic`; every repository has a NOT NULL `connectionId`. Device flow binds via `?connection_type=&connection_name=`; instance attribution is client-driven (`POST /connections/:id/adopt`), never guessed server-side. The `/connections` surface is open to every authenticated user. - **Feature flags**: registry in code (`@common/server` `FeatureFlags`) + strict-boolean per-user overrides; resolution `override ?? registry default`. Flags gate non-default connection *types* (`connection-restic`); admin-provisioned connections bypass. Boundary rule: env/cluster-settings = deployment config (ops-owned); feature flags = per-user product gating (admin-owned, runtime). - **Billing**: `ConnectionTypeInfos` declares metering tiers and `minObjectSizeBytes` floor; metrics-worker computes `billableBytes = max(size, objects * floor)` per connection. - **Revocation** (revocable types only, default `restic`): postgres is truth; michael checks L1 per-process cache → valkey verdict cache (`yucca:michael:verdict:`) → yucca-api introspection (`GET /internal/restic-tokens/:jti`). Revoke flips the DB row + best-effort DELs the valkey key; introspection outage honors the grace window then **fails closed**. Long-lived tokens are revocable-only. yuctl: `tokens list/revoke`, `repos url --ttl`. ## Infrastructure architecture Fleet model: **partition → region → { one K8s cluster, one-or-more Ceph clusters }**. Partitions `prod`/`staging`/`dev`; regions e.g. `htz-fsn1`, `austin`, `local`, plus `global`. Slug = `-`. - **`tf/`** — OpenTofu + Terragrunt (`deployment////`, shared logic in `shared/modules/`). Every stack emits a non-sensitive **`discovery` output** consumed by yuctl/k8s/ansible; secrets in it are `op://` refs. - **`ansible/`** — `ceph/` (cephadm bare-metal), `talos/` (Talos VMs on the Ceph hypervisors — VM provisioning only; talosctl is owned by the TF siderolabs provider), `mgmt/` (management hosts + NetBird). Roles gated by `*_enabled` flags. - **`kubernetes/`** — **Flux GitOps**. `clusters///` entry points; `apps/{base,,dev/local}/` HelmReleases; `components/` Kustomize Components per role. Config layers merged via postBuild: `cluster-settings.generated.yaml` (TF), `cluster-settings.yaml` (human), `image-versions` (CI). - **`charts/`** — in-repo Helm charts; `lib/yucca-common` library chart + `apps/` charts. Service names pinned via `fullnameOverride` so DNS is identical under Tilt and Flux. Deploy flow: merge to main → CI builds `0.0.` images → Flux auto-promotes to staging → prod is promoted by merging the release-please PR (stamps both prod pins under `kubernetes/clusters/prod/htz-fsn1/`). ## Conventions - **Conventional commits** enforced on PRs (`feat(scope):`, `fix(scope):`, `chore:`); scopes like `ceph`, `netbird`, `michael`, `yucca-api`, `ansible`, `bgp`. Releases via release-please; the monorepo is single-versioned. - **PR descriptions and commit messages: one line at most.** Always. The diff carries the detail; neither the PR body nor the commit message restates it. - ESLint is strict on promises (`no-floating-promises`, `no-misused-promises`, `require-await`, `await-thenable` are errors). Prettier: single quotes, trailing commas, width 120. - Generated files are eslint-ignored: `**/fetch-client.ts`, `packages/web/src/locales`, `dist`, `build`, `.svelte-kit`. - **Match the package you are in.** Read the surrounding code first; write code that looks like what is already there, not your own conventions. (And remember the life-or-death rule at the top: zero comments.) ### Naming - **Clusters are themed by workload**: Kubernetes → Star Wars (`luke` = staging, `father` = soon-to-be prod); Ceph → Dune (`sietch`, `spice`, …). - **Node hostnames**: `-----`, e.g. `yucca-int-aus-luke-k8s-`. Provider/region are the 3-letter codes from the region's `region.hcl` (austin = `int`/`aus`, htz-fsn1 = `htz`/`fsn`); role is `k8s` or `ceph`; `` auto-picks from `tf/shared/modules/node-names/wordlist.txt` (deterministic per cluster; pass an explicit `name` to override).