0014. Connettore dedicato per i file dei workspace Noeva
- Stato: Accettata
- Data: 2026-07-08
- Decisori: Team BI/ETL
Contesto
Serve un connettore per leggere/scrivere file sui workspace del prodotto Noeva (via noeva-server-api): risoluzione per id, per nome esatto, per ricerca fuzzy; upload con overwrite o nuova versione. Lo StorageProvider esistente (src/connectors/file/) è path-based (list/get/put) e non modella queste capacità — id/nome/fuzzy e overwrite/versione sono concetti del dominio "file di un workspace Noeva", non dello storage a byte generico.
L'API (noeva-server-api) espone:
GET /api/files/:fileId— metadati by id (envelope{ statusCode, message, payload }).GET /api/files/search?name=— solo substringILIKEserver-side, nessun ranking fuzzy (envelope{ success, data: { files, total_count } }— diverso da quello sopra).GET /api/files/:fileId/presigned-url— URL presigned per i byte originali (non/:fileId/content, che restituisce testo estratto/parsato, non i byte grezzi).POST /api/files/upload/prepare+POST /api/files/upload/complete/:fileId— upload a due fasi; i booleanioverwrite/versiondecidono sovrascrittura, nuova versione, o (nessuno dei due, su collisione nome) 409.- Nessun campo di checksum/hash esposto: per verificare se una nuova versione è identica alla corrente serve scaricare e hashare i byte lato client.
Decisione
- Famiglia di connettore dedicata (
src/connectors/noeva/), sul modello dipostgres/sqlserver(opzioni dedicate, client iniettato), non un'estensione delloStorageProvider: le operazioni (risoluzione per id/nome/fuzzy, overwrite/versione) non sono espresse dal contratto path-basedlist/get/put. - Risoluzione a singolo best-match:
noevaFileSourceconfileName/fuzzyQueryritorna un solo file (sopra soglia) o lancia un errore esplicito se ambiguo/assente — non una lista da disambiguare a valle. Più semplice da consumare in pipeline batch. - Ranking fuzzy client-side con
fuse.js: l'API fa soloILIKEsubstring, nessuna fuzzy search server-side.fuse.jsè isolato infuzzy.ts(resolveBestMatch), sostituibile senza toccaresource.ts/sink.tsse il ranking risultasse insoddisfacente. - Idempotenza upload "nuova versione" via hash-compare: prima di caricare, se esiste già un file con lo stesso nome,
noevaFileSinkscarica la versione corrente e confrontasha256con il nuovo contenuto; se identico, salta l'upload (written: 0). Evita versioni duplicate quando una pipeline viene rieseguita con lo stesso input. NOEVA_API_KEYdeve essere una API key service-role del workspace, non una per-user:files-search.controller.tsscopa la ricerca all'utente proprietario a meno che il flagisServiceRolesia vero. Un connettore ETL che deve poter risolvere/caricare qualunque file del workspace (non solo quelli di un singolo utente umano) richiede la visibilità service-role. Documentato in.env.example; da provisionare esplicitamente in fase di deploy (non è la stessa API key personale usata da un utente nell'app).- Client HTTP nuovo (
NoevaHttpClient), non unStorageProvider: nessun SDK TS riutilizzabile esiste nel monorepo per queste API. Modellato sunoeva-mcps/noeva-mcp-core/src/core/client.ts— parsing tollerante dell'envelope (data ?? payload ?? raw), necessario perché l'API non ha un envelope uniforme tra endpoint diversi (confermato leggendo i controller: search usa{ data }, upload/get usano{ payload }). - Validazione Zod delle risposte (
fileMetaWireSchema, ecc. inclient.ts): i dati esterni entrano comeunknowne vengono ristretti, come da regola generale del repo.
Alternative considerate
- Estendere
StorageProvidercon metodi opzionaliresolveByName/resolveFuzzy— scartata: avrebbe reso l'interfaccia generica dipendente da concetti (fuzzy, versioning) che solo Noeva ha, complicandofilesystem/s3/sftpsenza reale beneficio. - Fuzzy ranking multiplo (lista di candidati) invece di singolo best-match — scartata per lo scope attuale (uso in pipeline batch, non interattivo): un solo risultato o un errore esplicito tengono la pipeline deterministica.
- Nessuna idempotenza (upload incondizionato in
new-version) — scartata: avrebbe prodotto versioni duplicate a ogni retry/riesecuzione della pipeline con lo stesso input, violando la regola generale "rieseguire la pipeline non duplica dati".
Conseguenze
- Positive: risoluzione file per id/nome/fuzzy e upload overwrite/nuova-versione disponibili come
Source<NoevaFileContent>/Sink<Uint8Array>standard, componibili con iFileCodecesistenti a valle/monte per decodifica/codifica del contenuto. - Costi/limiti:
- Nuova dipendenza
fuse.js; isolata infuzzy.ts. - Hash-compare scarica per intero la versione corrente prima di ogni upload
new-version: costoso per file grandi (accettabile per i volumi ETL previsti; stesso limite ~50MB/no-TUS già noto innoeva-mcp-core). - Richiede il provisioning di una API key service-role in ogni ambiente (dev/staging/ prod) — un requisito di deploy in più rispetto a una chiave utente qualunque.
source.ts/sink.tsrestano esclusi dalla soglia di coverage (glob esistente invitest.config.ts), ma — a differenza di postgres/sqlserver — hanno comunque test unitari colocati con unNoevaHttpClientfake, essendo il client una nostra astrazione iniettabile e non un driver vendor.
- Nuova dipendenza

