Installation et exploitation
Ce document décrit comment provisionner, configurer, démarrer, utiliser, arrêter et dépanner le labo sur le NUC. Il ne contient aucun secret : il indique où se trouvent les identifiants, jamais leur valeur.
Prérequis
- Le NUC avec ses briques partagées en service :
pg-central,minio-central, LiteLLM, Traefik, et les réseaux Docker externesdataetweb. - Docker et Docker Compose.
uvet Python 3.12 (requires-python = ">=3.12,<3.13"danspyproject.toml).- Accès réseau sortant au premier usage : registre d'images, PyPI (via
uv), dépôt d'extensions DuckDB (INSTALL httpfsetINSTALL icebergdanslab/duckdb_connect.py), Hugging Face au moment de la construction de l'image de prévision.
Provisionnement des briques du NUC
Fait par l'administrateur du NUC, hors de ce dépôt (le dépôt ne contient aucune configuration de brique).
PostgreSQL et MinIO
cd ~/Documents/nuc.local
make pg-new PROJET=snowlab_polaris # base snowlab_polaris_db, rôle propriétaire snowlab_polaris_user
make pg-new PROJET=snowlab_marquez # base snowlab_marquez_db, rôle propriétaire snowlab_marquez_user
make minio-new PROJET=snowlab # bucket snowlab, utilisateur scopé snowlab (secret affiché une seule fois)
L'image minio/mc attendue par les scripts du NUC est introuvable sur les registres. Les scripts ont été lancés en surchargeant l'image cliente : MC_IMAGE=minio/minio:RELEASE.2025-04-22T22-12-26Z. Le job minio-init du labo n'utilise pas mc : il passe par l'API S3 avec amazon/aws-cli:2.38.5 (infra/minio/init.sh).
Le bucket snowlab porte deux préfixes, créés par minio-init sous forme d'objets .keep : landing/ (fichiers déposés, équivalent des stages) et warehouse/ (fichiers des tables Iceberg).
LiteLLM
Dix alias de chat pointent vers le même modèle local, ollama_chat/qwen3:8b, sans fallbacks vers un modèle cloud : cortex-classify, cortex-sentiment, cortex-complete, cortex-filter, cortex-extract, cortex-translate, cortex-summarize, cortex-agg, cortex-summarize-agg, cortex-redact. L'alias cortex-embed pointe vers ollama/bge-m3-cpu (1 024 dimensions). La clé du labo est limitée à ces onze alias cortex-* : un appel sur un alias cloud de la brique (claude-sonnet-4-6) est refusé (401, test d'intégration). Fenêtres mesurées le 2026-10-23 : 32 768 jetons pour le chat, 2 048 pour cortex-embed (voir cortex-lab.md).
- La brique LiteLLM fonctionne avec
store_model_in_db: true: les alias ont été ajoutés à chaud par l'API/model/new. Ils sont stockés dans la base de LiteLLM et n'apparaissent pas dansconfig/litellm/config.yamldu NUC. - Une clé virtuelle nommée
snowflake-lab-kit, limitée à ces trois alias, est utilisée par le labo (LITELLM_API_KEY).
Après toute action d'infrastructure sur le NUC, la règle globale demande une ligne dans ~/Documents/central/README.md et un commit (hors de ce dépôt).
Fichier .env
.env est à la racine du dépôt, n'est jamais commité (.gitignore) et sa lecture est interdite à Claude (.claude/settings.json). Il se construit à partir de .env.example, qui ne contient que des valeurs factices (contrôlé par tests/test_repo_conventions.py).
| Variable | Rôle | Origine de la valeur |
|---|---|---|
LAB_DATABASE |
Nom de la base Snowflake cible, donc du catalogue Polaris (le fichier de catalogue Trino porte ce nom) | Décision : analytics |
LAB_UID, LAB_GID |
uid et gid de l'utilisateur hôte, pour que secrets/polaris_credentials.env reste lisible |
id -u, id -g |
POLARIS_DB_NAME, POLARIS_DB_USER, POLARIS_DB_PASSWORD |
Base PostgreSQL de Polaris sur pg-central |
make pg-new PROJET=snowlab_polaris |
MARQUEZ_DB_NAME, MARQUEZ_DB_USER, MARQUEZ_DB_PASSWORD |
Base PostgreSQL de Marquez sur pg-central |
make pg-new PROJET=snowlab_marquez |
MINIO_ENDPOINT |
Point d'accès S3 vu depuis l'hôte (dans le compose : http://minio-central:9000) |
Brique MinIO |
MINIO_BUCKET |
Bucket du labo (snowlab) |
make minio-new PROJET=snowlab |
MINIO_ACCESS_KEY, MINIO_SECRET_KEY |
Clés de l'utilisateur scopé du bucket | make minio-new PROJET=snowlab |
MINIO_REGION |
Région S3 annoncée | Défaut us-east-1 |
MINIO_ENDPOINT_HOSTPORT |
Forme hôte:port de MINIO_ENDPOINT, lue par le profil dbt-duckdb |
Dérivée de MINIO_ENDPOINT |
POLARIS_URI |
URI de Polaris vue depuis l'hôte | http://127.0.0.1:18181 |
POLARIS_REALM |
Realm Polaris (en-tête Polaris-Realm) |
Défaut POLARIS |
POLARIS_ROOT_CLIENT_ID, POLARIS_ROOT_CLIENT_SECRET |
Identifiants root, utilisés seulement par polaris-bootstrap (fichier YAML --credentials-file rendu par Compose, pas d'argument de processus) et polaris-init |
Choisis par l'administrateur |
POLARIS_TOKEN_SIGNING_SECRET |
Clé symétrique de signature des jetons Polaris (32 caractères aléatoires ou plus) | Choisie par l'administrateur |
POLARIS_TECH_CLIENT_ID, POLARIS_TECH_CLIENT_SECRET |
Principal technique lab_tech |
Émis par Polaris au premier polaris-init, recopiés depuis secrets/polaris_credentials.env |
POLARIS_READER_CLIENT_ID, POLARIS_READER_CLIENT_SECRET |
Principal en lecture seule lab_reader |
Idem |
POLARIS_DROP_WITH_PURGE |
Purge à la suppression sur le catalogue (true ou false, défaut true), appliquée par polaris-init ; voir « Purge à la suppression » |
Choix de l'administrateur |
TRINO_URL |
URL de Trino vue depuis l'hôte | http://127.0.0.1:18080 |
MARQUEZ_URL |
URL de l'API Marquez vue depuis l'hôte ; active l'émission de lineage | http://127.0.0.1:15000 |
DOCS_URL |
URL du site de documentation, lue par les seuls tests d'intégration de tests/website ; aucune valeur secrète |
http://docs.openflake.nuc |
LITELLM_BASE_URL |
Seule URL vers un LLM (dans un conteneur du réseau data : http://litellm:4000) |
Brique LiteLLM |
LITELLM_API_KEY |
Clé virtuelle snowflake-lab-kit |
Brique LiteLLM |
CORTEX_COMPLETE_EXTRA_ALIASES |
Noms de modèles ajoutés à la liste d'alias autorisés d'AI_COMPLETE (par défaut les alias cortex-* connus ; séparés par des virgules) ; un nom hors liste donne NULL ou l'erreur du contrat, sans appel réseau |
vide |
CORTEX_COMPLETE_MODEL |
Nom de modèle passé à AI_COMPLETE par ingest/mapping.py (alias LiteLLM au labo) |
cortex-complete |
CORTEX_BACKEND |
Bascule à l'import de cortex_lab.compat : lab (défaut) ou snowflake |
lab |
Retiré le 2026-10-23 : DBT_DUCKDB_PATH et LAB_DUCKDB_PATH n'existent plus. dbt-duckdb travaille sur une base locale en mémoire et écrit dans Polaris ; le service de prévision lit Polaris. À supprimer d'un ancien .env : un fichier analytics.duckdb serait en conflit avec l'alias analytics de Polaris. |
| DBT_TRINO_HOST, DBT_TRINO_PORT, DBT_TRINO_USER | Cible trino du profil dbt | 127.0.0.1, 18080, lab_tech |
| SNOWFLAKE_ACCOUNT, SNOWFLAKE_USER, SNOWFLAKE_PASSWORD, SNOWFLAKE_ROLE, SNOWFLAKE_WAREHOUSE | Cible snowflake du profil dbt (côté client) | Fournis chez le client ; factices au labo |
Variables lues par le code mais fixées par le compose ou par défaut, non listées dans .env.example : POLARIS_INTERNAL_URI, MINIO_INTERNAL_ENDPOINT, POLARIS_CREDENTIALS_OUT, MINIO_WAREHOUSE_PREFIX, HF_HUB_OFFLINE, POSTGRES_* (Marquez), MARQUEZ_CONFIG, QUARKUS_CONFIG_LOCATIONS, LITELLM_TIMEOUT_SECONDS (défaut 120 secondes), DBT_SEND_ANONYMOUS_USAGE_STATS.
Premier démarrage
cd /home/stephanev/Documents/snowflake/snowflake-lab-kit
cp .env.example .env # puis remplacer chaque valeur factice
uv sync # Python 3.12, versions de uv.lock (groupe dev par défaut)
mkdir -p secrets
docker compose up polaris-init # minio-init, polaris-bootstrap, polaris, puis le job d'initialisation
Au premier passage, Polaris émet les identifiants des principaux lab_tech et lab_reader. Il ne les renvoie qu'une fois : infra/polaris/init_catalog.py les écrit dans secrets/polaris_credentials.env (mode 0600, jamais dans les logs). Recopier les quatre lignes POLARIS_TECH_CLIENT_* et POLARIS_READER_CLIENT_* dans .env, puis :
docker compose up -d # Trino et Marquez (Polaris reste démarré)
Le conteneur arrêté polaris-bootstrap garde le fichier des identifiants root (lisible par son seul utilisateur, mode 0400, mais extractible par docker cp pour qui accède au démon Docker). Le supprimer une fois la pile démarrée ; Compose le recrée au prochain up :
docker compose rm -f polaris-bootstrap
Les identifiants root (POLARIS_ROOT_CLIENT_ID, POLARIS_ROOT_CLIENT_SECRET) et POLARIS_REALM ne contiennent pas d'apostrophe : ils sont écrits dans un fichier YAML entre apostrophes.
Ordre exécuté par polaris-init : catalogue S3 analytics (emplacement s3://snowlab/warehouse/, path-style, sans STS), principal lab_tech, rôle de principal lab_tech_role, rôle de catalogue lab_tech_catalog_role, privilèges, namespaces (créés par lab_tech lui-même), puis principal lab_reader et ses droits.
Le job est rejouable : si les identifiants présents dans l'environnement fonctionnent encore, rien n'est tourné. S'ils manquent ou sont périmés, le principal existant est réinitialisé (/reset) et de nouveaux identifiants sont écrits dans secrets/polaris_credentials.env, à recopier de nouveau.
Démarrages suivants
docker compose up -d
L'état de Polaris (catalogue, namespaces, tables) est dans snowlab_polaris_db et survit au redémarrage du conteneur (test test_polaris_keeps_catalog_namespaces_and_tables_after_a_restart). Les données sont dans MinIO, le lineage dans snowlab_marquez_db. Les jobs minio-init, polaris-bootstrap et polaris-init se rejouent sans effet de bord.
Arrêt
docker compose stop # arrête les conteneurs, les garde
docker compose down # supprime les conteneurs et le réseau lab (Claude demande confirmation)
Aucune donnée n'est perdue par down : le compose ne déclare aucun volume, l'état est dans les briques du NUC (PostgreSQL, MinIO).
Accès
URL
| Service | Depuis l'hôte (scripts, tests, CLI) | Depuis le LAN (Traefik) | Depuis un conteneur |
|---|---|---|---|
| Trino (interface web et API) | http://127.0.0.1:18080 |
http://openflake.nuc |
http://trino:8080 (réseau lab) |
| Polaris (catalogue REST, OAuth) | http://127.0.0.1:18181 |
http://polaris.openflake.nuc |
http://polaris:8181 |
| Marquez (API OpenLineage) | http://127.0.0.1:15000 |
http://marquez.openflake.nuc |
http://marquez:5000 |
| Site de documentation | aucun port publié | http://docs.openflake.nuc |
http://docs:8080 (réseau web) |
| MinIO | MINIO_ENDPOINT |
brique du NUC | http://minio-central:9000 |
| LiteLLM | LITELLM_BASE_URL |
brique du NUC | http://litellm:4000 (réseau data) |
Les quatre routes Traefik (Trino, Polaris, Marquez et le site de documentation openflake-docs) passent par l'entrypoint web avec le middleware local-only@file (LAN et réseaux Docker seulement) et sans SSO, choix de Stéphane consigné dans l'avenant Traefik de PLAN.md. Conséquence acceptée : sur le LAN, Trino n'exige pas d'authentification et l'API Marquez accepte lectures et écritures ; Polaris reste protégé par OAuth. Trino honore les en-têtes X-Forwarded-* (http-server.process-forwarded=true).
Site de documentation
Le service docs sert, à http://docs.openflake.nuc, une page d'accueil de présentation et la documentation de docs/ (publiée sous /documentation/). Le site est statique : MkDocs avec le thème Material le construit en mode strict dans l'image (website/Dockerfile, assemblage par website/build.py), nginx non root le sert sur le port 8080 du conteneur, sur le réseau web seul, sans port publié sur l'hôte. Rien n'est chargé depuis Internet à la lecture : polices système, script Mermaid servi par le site (récupéré à la construction de l'image, version et empreinte SHA-256 fixées dans le Dockerfile).
Le site n'est pas reconstruit par docker compose up -d. Après toute modification de docs/ ou de website/ :
docker compose up -d --build docs
La construction échoue sur un lien ou une ancre cassés dans docs/, ou sur un nouveau fichier de docs/ absent de la navigation de website/mkdocs.yml. Construction locale sans Docker (le script Mermaid est à fournir) : uv run python website/build.py --output /tmp/site --mermaid <mermaid.min.js>.
Identifiants
| Accès | Mécanisme | Où sont les identifiants |
|---|---|---|
| Trino | Aucun mot de passe. Le nom d'utilisateur fixe les droits (infra/trino/rules.json) : lab_tech lit et écrit sur analytics sauf shared_* en lecture ; lab_reader lit semantic et shared_* ; tout autre utilisateur n'a accès à rien (sauf information_schema) |
Aucun |
| Polaris | OAuth2 client credentials : POST /api/catalog/v1/oauth/tokens avec l'en-tête Polaris-Realm et le scope PRINCIPAL_ROLE:<rôle> (lab_tech_role ou lab_reader_role) |
.env : POLARIS_TECH_*, POLARIS_READER_*, POLARIS_ROOT_* ; à l'émission, secrets/polaris_credentials.env |
| Marquez | API sans authentification, pas d'interface web | Aucun |
| MinIO | Clés statiques de l'utilisateur scopé snowlab |
.env : MINIO_ACCESS_KEY, MINIO_SECRET_KEY |
| LiteLLM | Clé virtuelle | .env : LITELLM_API_KEY |
| PostgreSQL | Rôles propriétaires des deux bases | .env : POLARIS_DB_*, MARQUEZ_DB_* |
Exemple d'obtention d'un jeton Polaris depuis l'hôte, sans écrire de secret dans la commande :
set -a; . ./.env; set +a
curl -s -X POST "$POLARIS_URI/api/catalog/v1/oauth/tokens" \
-H "Polaris-Realm: $POLARIS_REALM" \
-d grant_type=client_credentials \
-d client_id="$POLARIS_TECH_CLIENT_ID" \
-d client_secret="$POLARIS_TECH_CLIENT_SECRET" \
-d scope=PRINCIPAL_ROLE:lab_tech_role
Exemple de requête Trino en ligne de commande : trino --server http://127.0.0.1:18080 --user lab_reader --catalog analytics. Le client trino s'installe séparément : le JAR exécutable trino-cli-484 (la version du serveur), renommé en trino, avec Java 11 ou plus (https://trino.io/docs/current/client/cli.html, lue le 2026-10-23). Le client Python trino==0.340.0 du dépôt n'est pas cette CLI.
Utilisation courante
Produire et déposer des fichiers
uv run python -m data.generate landing --root /tmp/landing --as-of 2026-10-22 --history-days 7
Cette commande écrit les fichiers d'une date sur le disque local. Le dépôt dans MinIO n'a pas de commande dédiée : il passe par l'API Python LandingStore.put, comme dans tests/ingest/test_pipeline_integration.py :
from datetime import date
from data.generate import landing_files
from ingest.landing import LandingStore
from lab.config import LabConfig
day = date(2026, 10, 22)
store = LandingStore.for_minio(LabConfig.from_env())
for name, (source, content) in landing_files(day, history_days=7).items():
store.put(source, day, name, content)
(à lancer avec uv run --env-file .env python).
Charger en raw (Snowpipe manuel)
uv run --env-file .env python -m ingest.raw_loader --date 2026-10-22 [--source pms --source bookings] [--raw-schema raw]
La commande affiche, par fichier, le statut (loaded, skipped_already_loaded, rejected, unclassified), le nombre de lignes et la clé du fichier. Avec MARQUEZ_URL défini (dans .env), elle émet START puis COMPLETE vers Marquez pour chaque fichier chargé, avec le schéma réel en sortie (--raw-schema, défaut raw). Sans MARQUEZ_URL, elle journalise « lineage disabled, MARQUEZ_URL not set » et charge sans lineage, sans erreur. Écart corrigé le 2026-10-23 (voir collecte-retraitement.md).
Retraiter avec dbt
cp dbt/profiles.example.yml dbt/profiles.yml # aucun secret dans ce fichier (variables d'environnement)
uv run --env-file .env dbt build --project-dir dbt --profiles-dir dbt
dbt-duckdb lit les tables raw dans Polaris (attaché sous l'alias analytics) et y écrit les couches : analytics.standardized.*, analytics.semantic.*, seeds en analytics.semantic.* et analytics.intake.mapping_rules, lisibles par Trino et DuckDB. Une matérialisation table recrée la table à chaque exécution. Les fichiers des tables supprimées ou remplacées restent dans MinIO : constaté le 2026-10-23 (B7, tests/socle/test_stack_integration.py), DuckDB supprime sans purge, donc l'emplacement de la table remplacée et celui de la table supprimée gardent leurs fichiers ; aucun nettoyage n'est outillé.
Variante avec lineage : dbt-ol build --target duckdb ... avec OPENLINEAGE_URL pointé sur MARQUEZ_URL (et OPENLINEAGE_NAMESPACE pour l'espace de noms des jobs). Vérifiée le 2026-10-23 (B4, tests/ingest/test_dbt_lineage_integration.py) : les jobs et les datasets des modèles arrivent dans Marquez ; les datasets portent l'espace de noms duckdb://:memory:, pas iceberg://analytics comme ceux du chargeur, donc Marquez ne relie pas les jobs dbt aux jobs du chargeur. Les événements de test sont supprimés de Marquez à la fin du test (les espaces de noms vides restent listés).
Cible trino (compilation et exécution à dbt-trino) : vérifiée le 2026-10-23 et 2026-10-24 (B3, tests/ingest/test_dbt_trino_integration.py). dbt build --target trino passe grâce à la macro trino__list_relations_without_caching du projet (celle de l'adapter lit le catalogue system, refusé à lab_tech), et mf query répond sur cette cible si target: du profil vaut trino (la CLI mf n'a pas d'option --target). Remplacer une table ou un seed (seconde exécution) envoie à Polaris un DROP TABLE avec purge, que Polaris refusait (« Unable to purge entity ») : infra/polaris/init_catalog.py fixe désormais la propriété de catalogue polaris.config.drop-with-purge.enabled à true sur analytics (création, ou mise à jour d'un catalogue existant par docker compose up polaris-init, sans recréer Polaris). Conséquence : un DROP TABLE envoyé avec purge, donc par Trino, supprime les fichiers de la table dans MinIO ; DuckDB et PyIceberg ne demandent pas de purge, leurs fichiers orphelins (B7) restent.
Prévoir
docker compose --profile forecast build forecast
docker compose --profile forecast run --rm forecast forecast --as-of 2026-10-22
docker compose --profile forecast run --rm forecast backtest --as-of 2026-10-22 --origins 6 --step-days 14
Le conteneur lit les couches standardized et SCD2 et le calendrier dans Polaris (aucun volume local), ne fait aucun appel réseau pour l'inférence (HF_HUB_OFFLINE=1) et écrit dans analytics.forecast.*. Voir prevision.md.
Lancer les tests
uv run --env-file .env pytest -q -m "not integration"
uv run --group forecast --env-file .env pytest -q -m integration
Voir tests-et-gate.md.
Purge à la suppression
La variable POLARIS_DROP_WITH_PURGE de .env fixe la propriété de catalogue polaris.config.drop-with-purge.enabled du catalogue du labo.
| Valeur | Effet |
|---|---|
true (défaut) |
Un DROP TABLE envoyé avec purge, donc par Trino et dbt-trino, supprime définitivement les fichiers de la table dans MinIO. dbt build --target trino peut remplacer ses tables. |
false |
Polaris refuse un DROP TABLE avec purge : les fichiers restent dans MinIO et une table supprimée reste récupérable à la main. dbt-trino ne peut plus remplacer une table ni un seed ; les tests du second build trino sont alors sautés. |
DuckDB et PyIceberg suppriment sans purge, quelle que soit la valeur.
Risque, accepté par Stéphane le 2026-10-24 avec la valeur true : Trino est exposé sur le LAN sans authentification (http://openflake.nuc), et tout client qui se déclare lab_tech peut effacer définitivement les tables hors shared_*.
Changer la valeur :
# 1. modifier POLARIS_DROP_WITH_PURGE dans .env, puis
docker compose up polaris-init
Le job met à jour la propriété du catalogue existant sans recréer Polaris ni Trino, et l'écrit dans son journal (« updated polaris.config.drop-with-purge.enabled »).
Dépannage
| Symptôme | Cause | Remède |
|---|---|---|
Trino répond 401 en interrogeant le catalogue analytics |
La clé de signature POLARIS_TOKEN_SIGNING_SECRET a changé : les jetons détenus par Trino ne sont plus valides pour Polaris (token broker symmetric-key) |
Recréer Polaris et Trino ensemble : docker compose up -d --force-recreate polaris trino |
Trino ou un client répond 401 après un nouveau polaris-init |
Les identifiants de lab_tech ont été réinitialisés et .env n'est pas à jour |
Recopier secrets/polaris_credentials.env dans .env, puis docker compose up -d (recrée trino avec le nouvel environnement) |
PyIceberg ou un appel REST reçoit 400 no credentials are available |
Le client demande des identifiants vendus (en-tête X-Iceberg-Access-Delegation: vended-credentials) alors que le catalogue est stsUnavailable |
Ne pas demander de vending : lab/iceberg.py envoie cet en-tête vide, DuckDB utilise ACCESS_DELEGATION_MODE 'none', Trino iceberg.rest-catalog.vended-credentials-enabled=false ; les moteurs utilisent les clés statiques MinIO |
Les scripts MinIO du NUC échouent à tirer minio/mc |
Image introuvable sur les registres | Lancer les scripts avec MC_IMAGE=minio/minio:RELEASE.2025-04-22T22-12-26Z ; le labo, lui, utilise amazon/aws-cli |
minio-init sort en erreur bucket snowlab is not reachable with the scoped user |
Bucket ou utilisateur non provisionné, ou clés fausses dans .env |
make minio-new PROJET=snowlab sur le NUC, vérifier MINIO_ACCESS_KEY et MINIO_SECRET_KEY |
| Conflit de port à la publication | Ports déjà pris sur le NUC : 127.0.0.1:8080 et 127.0.0.1:3000 (autres piles), 5432, 9000, 4000 (briques) |
Le labo publie 18181 (Polaris), 18080 (Trino), 15000 (Marquez) ; tests/test_repo_conventions.py refuse tout port de la liste NUC_TAKEN_PORTS |
docker compose up échoue avec set ... in .env |
Variable obligatoire absente (syntaxe ${VAR:?...} du compose) |
Renseigner la variable nommée dans .env |
polaris-init échoue avec Polaris did not answer with a root token |
Polaris ne démarre pas (base PostgreSQL injoignable, identifiants root différents de ceux du bootstrap) | docker compose logs polaris polaris-bootstrap ; vérifier POLARIS_DB_* et POLARIS_ROOT_* |
secrets/polaris_credentials.env illisible |
Fichier écrit par un uid différent | Renseigner LAB_UID et LAB_GID dans .env |
| Tests d'intégration tous sautés | Les services ne répondent pas en TCP sur les URL de l'environnement (POLARIS_URI, TRINO_URL, MINIO_ENDPOINT, MARQUEZ_URL, LITELLM_BASE_URL) ; les tests du site de documentation lisent DOCS_URL |
Démarrer la pile et lancer pytest avec --env-file .env |
| Tests de prévision réelle sautés | tfc-t0 absent, ou poids t0-gamma absents du cache Hugging Face local à la révision épinglée |
uv run --group forecast ... et télécharger une fois les poids à la révision de forecast/model.py |
Erreur au premier connect() DuckDB hors ligne |
Les extensions httpfs et iceberg se téléchargent au premier INSTALL |
Lancer une fois avec accès réseau |
Une fonction AI_* renvoie NULL partout |
Erreur par ligne (alias absent, clé refusée, sortie invalide) masquée par le contrat Snowflake | Rejouer avec return_error_details à TRUE pour lire le champ error |
| Pas d'interface Marquez | Choix : API seule | Interroger http://127.0.0.1:15000/api/v1/namespaces/snowlab/jobs |