v0.2.40
LATESTCROSS-ENGINE WRITES, NOTHING SILENT
The write release. DVT has always read across every engine — now it writes across them too, through one battle-tested lane: dbt-compatible pre- and post-hooks on federated models (pre-hooks on SOURCE engines, post-hooks on targets — including pinned custom targets), dvt exec for one engine-native statement on any connection, an Explorer grid editor that previews the exact SQL before an explicit APPLY, and opt-in, key-gated DML endpoints on the api-portal. Underneath: every secret moved out of loose files into one machine store, the app suite always serves the project you launched it from, and a full audit closed every gap between the connection surface Settings advertises and what every lane actually honors — nothing connects silently wrong anymore, and nothing fails silently either.
ADDED
- ▸Hooks on federated models, dbt semantics in all cases: a plain pre_hook/post_hook string runs on the model's EFFECTIVE target (pinned target= or the default), and a {"sql": ..., "connection": "..."} hook — the DVT extension — runs on any named connection. Capture the source engine's own clock in a pre-hook before extraction, stamp the target after the load lands, audit both ends of a pipeline from the model that owns it. {{ this }}, {{ target }} and {{ run_started_at }} render; hook SQL executes verbatim on its engine (never transpiled); order and index are honored; a failing pre-hook stops the model before any read, a failing post-hook fails the node honestly while naming that the table already landed. The run line shows [hooks: N pre, M post].
- ▸dvt exec -t <connection> "sql" — one engine-native SQL statement on ANY named connection. SELECTs print rows (--limit, --json); DML and DDL execute with the engine's affected-row count; buckets and unknown connections are refused with the available outputs listed.
- ▸The Explorer grid editor: browse a table (or deep-link straight to it — #connection/schema/table/edit), toggle ✎ EDIT, change cells, add and delete rows. Every pending change renders as the EXACT SQL that will run — one builder serves the preview and the execution, so the preview cannot lie — keyed by the discovered primary key (🔑 in the header) or every original column value on engines without key metadata. Keyless UPDATE/DELETE are refused server-side. Nothing executes before an explicit APPLY, and per-statement affected counts make a key that matched 0 or 2+ rows visible. The raw-SQL box is now ENFORCED read-only.
- ▸api-portal writes: POST / PATCH / DELETE on /api/v1/models/<name>/rows, landing on the model's materialized table on its effective target. Off by default, and refused entirely unless API-key enforcement is ON — anonymous DML does not exist. A separate WRITES toggle per model, columns bound by the allow-list, a mandatory where on update/delete, the exact executed SQL in every response, and an audit log of every write.
- ▸SFTP as a bucket connector — host/user (+ password or private key) in Settings, sources and targets through the same Sling lane as S3/GCS/Azure.
- ▸The machine store: every DVT secret (AI credentials, login, license, machine id) now lives in ONE SQLite file at ~/.dvt/home.db, chmod 600 — not loose YAML files. Existing files migrate on first touch and are removed after a verified import; a file recreated by an older DVT version wins as the freshest intent and is consumed too, so mixed-version machines converge on their own.
- ▸The suite serves the project you're in: launching any app — or dvt serve — evicts an instance serving a DIFFERENT project from its port and takes over, with the eviction named out loud. No more one project's catalog, profiling or API showing up under another project's hub.
CHANGED
- ▸profiles.yml lives at ~/.dbt only — the ONE DVT-relevant file outside your project folders, kept there deliberately for dbt backward compatibility. The legacy ~/.dvt fallback is gone everywhere.
- ▸The catalog is loud about gaps: any connection dvt docs generate cannot reach — wrong auth, missing driver, no metadata lane — now warns with the consequence spelled out ("its relations will have NO columns in the catalog") instead of degrading silently.
- ▸Redshift catalog collection moved to redshift-connector (the driver dvt sync actually installs), with IAM auth supported; Redshift's Sling lane now defaults to sslmode=require.
- ▸Profiles carrying {{ env_var('...') }} render before use in the catalog's native lane — drivers no longer receive the literal jinja string.
FIXED
- ▸The auth matrix — a full audit of the connection surface Settings advertises against what every lane honors, every gap closed. Field aliases the UI and dbt actually write: mysql-family server/username/schema, SQL Server & Fabric server (with host\instance split), ClickHouse schema, BigQuery project, SQLite schemas_and_paths (database is a label, not a path). Credentials that were silently dropped: BigQuery and GCS keyfiles rode URL parameters that don't exist in Sling. Auth variants that hard-failed: Snowflake SSO and OAuth token, SQL Server Windows-trusted and both Entra ID forms, Oracle EZConnect / TNS descriptors / TCPS, Trino HTTPS (without which LDAP could never authenticate), Athena temporary credentials, Azure SAS and service principals, SFTP private keys, MotherDuck paths, Postgres and MySQL TLS material.
- ▸Three variants Sling genuinely cannot do — Redshift IAM, Databricks OAuth, Trino JWT — now raise specific errors naming the dbt-lane alternative instead of failing cryptically.
- ▸Snowflake keypair auth reaches the catalog's native metadata lane — keypair-only warehouses no longer produce a catalog with zero Snowflake columns (docs, api-portal and profiling all inherit the fix). Four engines gained native catalog branches that simply didn't exist: BigQuery, Trino, Athena, DuckDB. Oracle's metadata query matches schemas case-insensitively.
- ▸dvt sync installs what DVT advertises: the Athena adapter and driver, mariadb/mysql5 aliases, the Trino driver — and a failed driver install now reports failure instead of claiming success.
- ▸Retract on GCS actually authenticates with the profile keyfile and no longer loses the bucket prefix (it could report ABSENT while the object existed). SFTP targets can never be sent DROP TABLE statements.
- ▸dbt-oracle's DPY-4021 ("invalid protocol none") is shielded: Settings prefills protocol: tcp for new Oracle connections and dvt debug names the one-line fix when it sees the marker.
- ▸Concurrent app registration is file-locked — eight apps registering in the same second no longer lose entries (which read as unknown holders and caused needless evictions).
Get it: pip install dvt-core==0.2.40 · wheels on PyPI for macOS (arm64 + x86_64) and Linux, Python 3.10–3.12.