feat/worker-db-role-separation #10

Merged
AzSiAz merged 10 commits from feat/worker-db-role-separation into main 2026-07-16 23:41:49 +02:00
Owner

feat: séparation du rôle PostgreSQL DDL/runtime du worker

Sous-projet B du durcissement infra (A = pods K8s + distroless, mergé). Le worker — le processus qui fetch l'internet — se connectait avec le rôle owner du schéma (pleins pouvoirs DDL). Il tourne désormais sous indara_worker, un rôle DML-on$

Spec : docs/superpowers/specs/2026-07-16-worker-db-role-separation-design.md · Plan : docs/superpowers/plans/2026-07-16-worker-db-role-separation.md

Ce qui change

  • Migration 20260716000001_worker_role.sql : rôle indara_worker (LOGIN restreint, NOBYPASSRLS, zéro membership — la migration échoue si une existe), grants exacts de la surface pipeline (documents SELECT+UPDATE, document_sources S$
  • Accès pg-jobs : via le mécanisme existant de la lib — indara-migrate étend son pg_jobs::access::reconcile à indara_worker (aucun changement pg-jobs ; profil runtime, jamais jobs._sqlx_migrations).
  • Worker : nouvelle env DATABASE_WORKER_URL, pool épinglé (search_path, row_security) comme le server, et garde de boot (indara_db::role_guard) miroir de celui du server — refuse superuser/BYPASSRLS/ownership/CREATE/toute membersh$
  • Chart : database.workerExistingSecret/workerSecretKey requis, sans fallback sur l'owner ; indara.databaseEnv (owner) n'est plus consommé que par le job de migrations. README chart : provisioning CNPG complet du rôle + secrets.
  • Dev : init-runtime.sql provisionne indara_worker ; le superuser dev est refusé par le garde (voulu — mêmes invariants qu'en prod).

Breaking changes (release notes)

  1. database.workerExistingSecret requis — provisionner le rôle indara_worker (CNPG managed role, le README du chart donne le YAML) et son secret URI avant l'upgrade. Aucune membership à granter.
  2. Le worker refuse un credential superuser/owner au boot.

L'ordre d'upgrade est sûr : migration (rôle+grants+policies) → migrations pg-jobs → reconcile tournent dans le job pre-upgrade avant le rollout des pods.

Vérification

  • Revue par task + revue finale whole-branch : la surface grantée = la surface réellement requêtée par le worker, vérifiée fonction par fonction (indara-jobsindara_db::documents, 11 fonctions) ; policies worker-scoped sans fuite vers les $
  • Tests : cargo test --workspace vert, clippy 0 warning, 3 tests RLS --ignored verts contre la base dev migrée (contrat worker + garde + matrice server), helm lint OK.
  • E2E réel : assertions SQL exactes (2 | t | f | t | f | 0), boot sain sous indara_worker (pipeline + S3), boot refusé sous postgres (message du garde, exit 1).
  • La revue par task a attrapé et corrigé un bug de fixtures (fuite d'un tombstone par run de test — ordre cleanup/cascade) ; la revue finale a fait réaligner le README racine.

🤖 Generated with Claude Code

# feat: séparation du rôle PostgreSQL DDL/runtime du worker Sous-projet B du durcissement infra (A = pods K8s + distroless, mergé). Le worker — le processus qui fetch l'internet — se connectait avec le rôle **owner** du schéma (pleins pouvoirs DDL). Il tourne désormais sous `indara_worker`, un rôle DML-on$ Spec : `docs/superpowers/specs/2026-07-16-worker-db-role-separation-design.md` · Plan : `docs/superpowers/plans/2026-07-16-worker-db-role-separation.md` ## Ce qui change - **Migration `20260716000001_worker_role.sql`** : rôle `indara_worker` (LOGIN restreint, NOBYPASSRLS, **zéro membership** — la migration échoue si une existe), grants exacts de la surface pipeline (`documents` SELECT+UPDATE, `document_sources` S$ - **Accès pg-jobs** : via le mécanisme existant de la lib — `indara-migrate` étend son `pg_jobs::access::reconcile` à `indara_worker` (aucun changement pg-jobs ; profil runtime, jamais `jobs._sqlx_migrations`). - **Worker** : nouvelle env `DATABASE_WORKER_URL`, pool épinglé (`search_path`, `row_security`) comme le server, et **garde de boot** (`indara_db::role_guard`) miroir de celui du server — refuse superuser/BYPASSRLS/ownership/CREATE/toute membersh$ - **Chart** : `database.workerExistingSecret`/`workerSecretKey` requis, **sans fallback** sur l'owner ; `indara.databaseEnv` (owner) n'est plus consommé que par le job de migrations. README chart : provisioning CNPG complet du rôle + secrets. - **Dev** : `init-runtime.sql` provisionne `indara_worker` ; le superuser dev est refusé par le garde (voulu — mêmes invariants qu'en prod). ## Breaking changes (release notes) 1. `database.workerExistingSecret` **requis** — provisionner le rôle `indara_worker` (CNPG managed role, le README du chart donne le YAML) et son secret URI avant l'upgrade. Aucune membership à granter. 2. Le worker **refuse un credential superuser/owner au boot**. L'ordre d'upgrade est sûr : migration (rôle+grants+policies) → migrations pg-jobs → reconcile tournent dans le job pre-upgrade avant le rollout des pods. ## Vérification - Revue par task + revue finale whole-branch : la surface grantée = la surface réellement requêtée par le worker, vérifiée fonction par fonction (`indara-jobs` → `indara_db::documents`, 11 fonctions) ; policies worker-scoped sans fuite vers les $ - Tests : `cargo test --workspace` vert, clippy 0 warning, 3 tests RLS `--ignored` verts contre la base dev migrée (contrat worker + garde + matrice server), `helm lint` OK. - **E2E réel** : assertions SQL exactes (`2 | t | f | t | f | 0`), boot sain sous `indara_worker` (pipeline + S3), boot **refusé** sous `postgres` (message du garde, exit 1). - La revue par task a attrapé et corrigé un bug de fixtures (fuite d'un tombstone par run de test — ordre cleanup/cascade) ; la revue finale a fait réaligner le README racine. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
AzSiAz self-assigned this 2026-07-16 23:07:58 +02:00
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Le DELETE documents cascade sur document_sources dont le trigger tombstone
la clé /b survivante ; balayer snapshot_tombstones avant laissait une ligne
orpheline par exécution. Le sweep passe après la cascade et verrouille
l'ordre avec rows_affected == 1.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Corrige deux passages obsolètes du README racine qui attribuaient encore
l'owner `DATABASE_URL` au worker, et réécrit la doc de module de
`role_guard.rs` pour énumérer honnêtement ses divergences vs le guard serveur.

Seul le binaire de migration utilise l'owner `DATABASE_URL` ; le worker passe
par `DATABASE_WORKER_URL` comme `indara_worker`, rôle DML-only sans membership,
son boot guard refusant un credential superuser/owner. Le guard worker omet
aussi, volontairement, le pin du nom de rôle, les checks login/direct-login et
`runtime_is_restricted` (le worker ne fait jamais `SET ROLE`).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AzSiAz merged commit fcaddd26e1 into main 2026-07-16 23:41:49 +02:00
AzSiAz deleted branch feat/worker-db-role-separation 2026-07-16 23:41:49 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
AzSiAz/Indara!10
No description provided.