Skip to content

Releases: Sheetaldharshan200/Exasol-studio

Exasol Studio v2026.10.0

Choose a tag to compare

@github-actions github-actions released this 27 Sep 18:08
4a9e425
Exasol Studio 2026.10.0

UDF editor: language-aware hints, context linting, AI ghost text.
MongoDB as a data source; Virtual Schemas shelf; full ecosystem catalogue.
GitHub: release reads fall back to github.com when the API allowance is spent;
optional token to raise the limit; org-listing metadata (149 -> 4 requests).

Exasol Studio v2026.9.0

Choose a tag to compare

@github-actions github-actions released this 23 Sep 16:40
dbf213a
Visualizer, results and local-runtime work — release 2026.9.0 (#159)

* feat(studio): dashboards, folder data-loading, setup self-heal, and fixes

Dashboards
- Freeform AI-first dashboard system (features/dashboard): JSON-doc model,
  canvas, widgets, cross-filter, drill-down, snapshot/export, live share.
- Interactive echarts: persistent instance + setOption (no re-init flicker),
  category-axis fix, toolbox hidden while editing, report tooltips.
- Fixed-width dashboard; text widgets hug content; per-chart "Edit in notebook".
- Cross-filter only targets dimensions (never measures); animated filter bar.

Assistant / data loading
- Folders attach as ONE collapsible chip (composer + sent message); files keep
  their subfolder path, saved without collision; import_csv accepts a saved
  path (contained to the attachments dir) so a whole datasets/ tree loads,
  subfolder -> schema, file -> table.
- App-control gate fixed: read config.settings().appControl (was always undef).
- Send is blocked with no connected model (no hang); model pill flags reconnect.
- Shield "Save" shows applying state and surfaces errors like the sandbox.

Local database setup
- Self-heal: a reused/adopted stale daemon that isn't query-ready triggers a
  clean managed redeploy (force-stop + reclaim port), at SYS and MCP levels.
- Readiness patience is deadline-tuned: fast-fail an adopted daemon, wait out a
  fresh cold boot.
- Fix stale arm64 executableSha256 pin for Exasol Personal v2.2.0 (verified
  against the official archive).

Sidebar / Marketplace
- Dedup the managed-local "Exasol Personal (local)" so it shows once; card
  connects when the DB is reachable.
- Marketplace catalog syncs in the background (6h poll) and raises an updates
  badge; the open Updates tab refreshes and keeps last-known upstream tags.

Tests: agent-core 243, desktop 371 (added config, folder-attachment, updates,
attachment-routing coverage). tsc + cargo check clean.

* fix(marketplace,assistant): managed-component update on cards; unstick shield Save

- Managed components (e.g. MCP server) now show an available update ON their
  Marketplace card using the SAME live upstream the Updates tab uses, with an
  "Update to <ver>" button that routes to the Updates tab — no longer falsely
  reads "Up to date" while the Updates tab has a newer version.
- Shield Save: stop gating the button on the applying state (a hung/pending
  engine grant-sync left Save permanently disabled). Save is enabled whenever
  there are changes again; the applying indicator is non-blocking with a 25s
  safety timeout so it can never stick.

* fix(marketplace): stop periodic GitHub-API polling from exhausting the rate limit

The Updates tab and cards read "latest" from unauthenticated GitHub API calls
(marketRelease / componentsUpstream), capped at 60 requests/hour. The periodic
syncs I added re-fired those on 10m/5m timers, burning the quota to zero — after
which every release lookup fails and the Updates tab shows nothing (even for a
real update like MCP 2.2.0).

- Marketplace periodic sync now refreshes ONLY the rate-limit-free catalog.json
  mirror + local installed/component reads — never the per-repo GitHub API.
- IndependentComponents periodic refresh re-reads the local installed list only;
  upstream tags are fetched once on mount (+ manual Refresh), not on a timer.
- Live release fetches stay mount-only; the 6h background badge poll is unchanged.

* fix(marketplace): install-into targets are the LIVE connection list, no managed dupe

The Semantic Views "Install into" picker listed a hardcoded "Local database
(managed)" PLUS the managed local profile ("Exasol Personal (local)") — the same
database twice — and was fetched once on mount.

- Exclude the managed local DB from the profile list robustly (notes marker OR
  name "Exasol Personal (local)" + local host + SYS user), so it appears only as
  the single "Local database (managed)" entry.
- Read targets LIVE from the connection profiles: refresh on the picker opening
  and on connection add/remove/reconfigure and DB-state events (event-driven,
  no timer) — mirrors the Connections tab.

* fix(setup): recover a credential-desynced local DB via the master password

A reused/adopted local database whose SYS password was changed to the master
password (the unified-credential model) failed setup with 'authentication
failed' — the readiness probe only tried the stale deploy-time credential, and
the adopted self-heal redeployed without ever trying the master. Now the
adopted path tries the master-password recovery FIRST (cheap, no redeploy) and
falls back to it again after a redeploy, so a healthy-but-desynced DB reconciles
instead of failing.

* fix(assistant): Claude Pro/Max OAuth always shows the code-paste box

Selecting the Claude Pro/Max (subscription) auth method could hang on 'Waiting
for authorization…' with no way to enter the code: startOauth blocked in the
waiting state for 'auto' flows, and the paste box only rendered for method
'code'. Now the browser opens, state returns to idle immediately, any auto
callback is polled in the BACKGROUND, and the code-paste box is always offered
for an active flow — so the sign-in can always be completed.

* fix: keep model menu open during OAuth; tighten DB readiness waits

OAuth: opening the browser blurs the app window, which Radix treats as an
outside interaction and dismissed the model menu — taking the code-paste box
with it, so the code could never be entered. Keep the menu (and its connect
submenu) open whenever an OAuth flow is active: guard onOpenChange and the
content's onInteractOutside/onFocusOutside/onPointerDownOutside/onEscapeKeyDown.

DB setup: the redeploy readiness wait was 180s with 5s polls even though the DB
is ready ~15s after a restart — so a credential mismatch stalled for minutes.
Cut the redeploy deadline to 60s and the poll to 3s; on failure it already falls
through to the master-password recovery.

* fix(setup): use our own deployment directly — never adopt it via the registry

Setup 'adopted' Studio's own managed database through the shared registry,
whose stored credential drifts (stale sys password / old MCP identity). The
readiness probe then failed and every launch crawled through the stop/start
recovery ladder before coming up.

ensure_runtime now short-circuits: when Studio's own deployment exists, go
straight to ensure_personal — it reuses the running database with NO restart
and reads the credential from the deployment's own secrets.json (the on-disk
source of truth, kept in sync after every ALTER USER). Registry adoption
remains only for genuinely foreign local databases.

* feat(agent): master scenario skillset — route every intent to the right tool

Six builtin skills that make the agent act agentically instead of guessing:
- scenario-router: THE intent matrix (load/federate/schedule/dbt/ETL/visualize/
  semantic/ML/export/catalog) with agentic ground rules — clarify once, plan,
  act with tools, verify with real results, never dead-end.
- exasol-federation: multi-source questions — clarify sources, import+join on
  Personal, virtual schemas on full Exasol, honest snapshot-vs-live wording.
- exasol-scheduling: dashboard auto-refresh vs exasol-scheduler daemon
  (table-driven SQL jobs, AFTER chains, history table) vs launchd+exapump on
  laptops (missed runs are not replayed by the daemon).
- exasol-dbt: official dbt-exasol adapter with the verified Personal profile
  (encryption + validate_server_certificate: false), seed CSV pitfalls,
  partition/distribute configs.
- exasol-etl-orchestration: stage -> transform -> schedule -> monitor composed
  from the other skills, idempotent stages, real-count reporting.
- exasol-community-upgrade: the never-say-can't ladder — check Docker, install
  Colima with permission, deploy Community Edition with permission, run
  blocked capabilities (virtual schemas, kafka, BucketFS) there.

Facts grounded against exasol-labs repos, dbt-exasol README, and the Personal
Local capability matrix. skills.builtin.test.ts validates every builtin skill
parses, names are unique, the set is present, and load_skill references can't
point at phantom skills (it caught one). Seed flag bumped to v2 so existing
installs adopt the new defaults.

* feat(marketplace): Exasol Community database card + broader component catalog

Exasol Community (exasol/docker-db — full Exasol 8 with BucketFS, <=10 GiB) is
now a first-class Marketplace database with a real lifecycle:

- Rust community_db module: Docker checks (installed -> engine running ->
  platform), LIVE version tags from Docker Hub (no GitHub rate limits; pure
  version_tags filter/sort with unit tests), pull + run using the officially
  documented flags (--privileged, --stop-timeout 120, data volume at /exa,
  DB on 127.0.0.1:8574, BucketFS on 2581), start/stop/remove/destroy, streamed
  progress over market:log, and automatic registration of the sys connection
  (generalized profiles::ensure_local_profile).
- CommunityDbActions card UI: per-state actions (install Docker guidance ->
  start engine -> version picker + Install & run -> running badge with
  Connect/Stop/Remove), live boot log, honest Apple-Silicon emulation warning
  (image is linux/amd64; upstream supports Docker on Linux).
- Catalog: exasol-community entry plus verified new components and drivers —
  notebook-connector, dbt-exasol, exasol-scheduler, dash-server,
  grafana-datasource, tableau-connector, terraform-provider-exasol,
  exa-postgres-interface, exasol-mongodb-vs, more-functions; CI catalog
  workflow items extended to carry their metadata.
- exasol-community-upgrade skill corrected to the verified reality: the Docker
  path is exasol/docker-db via the new card; the Community Edition OVA is a
  VirtualBox/VMware image (Intel-only on Mac).

Tests: rust 96 (3 new), desktop 371, agent-core 247 — all green.

* fix(marketplace): honest Community card, no more 'coming soon', do-it Docker setup, earlier connection save

- Catalog resolve precedence: explicit item name/description now OVERRIDE the
  GitHub About line (exasol/docker-db's says 'Documentation for…', which
  mislabeled a full database). The Community card reads 'Exasol Community —
  full Exasol 8 database in Docker…'.
- Removed the derived 'coming soon' state entirely: reference items (scheduler,
  grafana, tableau, terraform, postgres-interface, mongodb-vs, more-functions,
  dash-server, drivers) now show their real 'Get' action opening the official
  page instead of claiming they are unavailable.
- Docker prerequisites are now BUTTONS, not quotes: 'Install Docker' runs
  brew install colima docker + colima start (headless, streamed log), 'Start
  Docker' runs colima start or launches Docker Desktop and waits for the
  engine — with the equivalent command shown for transparency.
- Personal bootstrap: save the built-in connection profile the moment the
  database is query-ready — before the ExaPump/agent-skills downloads — so the
  user can connect minutes earlier on a fresh setup; launches with a completed
  setup keep the existing fast path (start DB only, verify in background).

* feat(marketplace): real installs for every driver/extension, honest updates, fetch-on-demand

Installs
- pickAsset extracted pure to marketplace/assets.ts (checksum/metadata filter,
  os+arch matching, null when a platform-tagged release has no build for this
  host) with 5 unit tests; scheduler/grafana/tableau/postgres-interface/
  mongodb-vs flipped from reference to real binary installs
- driver-jdbc installs from Maven Central: tested <latest> parser, jar
  downloaded into the managed folder, manifest records the REAL version
- binary items with no build for this host show 'No macOS build yet' + Get
  link instead of an Install button that could only fail

Updates
- catalog.json semantics changed (PR #144): 'latest' is always the real
  upstream release tag; the validated lock pin moved to a new 'verified'
  field — the old design hid MCP Server 2.2.0 behind the 2.0.0 pin everywhere
- components_upstream falls back to the catalog mirror when the
  unauthenticated GitHub API is rate-limited (pure mapper, tested)
- update state fetches ONLY at app open and on Updates/catalog tab clicks:
  removed the Marketplace 10-min sync, the components 5-min interval, the
  badge 6h loop and the Rust updates.rs 6h watcher

UI
- card actions pinned to the card bottom so buttons align across grid rows
- multi-select install: card checkboxes + floating 'Install selected' bar
  (parallel queue, duplicate-proof via an active-installs ref, eligibility
  re-checked at install time)
- multi-select update in Components: row checkboxes, Select all, sequential
  'Update selected' (the DB engine update takes a maintenance lock)

Self-heal
- personal initial readiness deadline 150s -> 45s: the launcher already waits
  for DB-ready, so the long patience only postponed the restart that heals a
  wedged daemon resetting TLS handshakes ('Connection reset by peer')

CI
- refresh-runtime-components no longer stages the removed
  exasol-semantic-views vendor dir (daily exit-128 failure since Sep 4);
  Linux validation clones the repo fresh (PR #145)
- update-catalog workflow regenerates latest+verified and keeps the
  open-PR-only guard from #142

* feat(marketplace): any-version installs everywhere + Studio & Exa engine update cards

Any version, live
- market_versions command lists real versions on demand: GitHub release tags
  (binary items), PyPI releases (pip items — yanked/fileless versions
  filtered), Maven Central (<version> list) — pure parsers/sorters tested
- every installable card gets a version dropdown (fetched on first open, never
  on a timer); a chosen tag resolves THAT release's assets via
  market_release(repo, tag) so the download matches the pick
- pip installs honor the chosen version (pkg==v), fall back to the verified
  pin, else latest; dbt-exasol and notebook-connector finally get real
  uv-pip installer arms (they previously fell through to the asset fallback)
- managed components on the Updates tab get the same dropdown (GitHub tags
  for personal/exapump/exa-agent, PyPI for the MCP server) — update_component
  installs the picked official version digest-verified

Studio + Exa engine
- StudioUpdateCard: Studio's own version card on the Updates tab — live check
  against the signed release, Download (byte progress) → Install → Restart;
  restart is safe because workspace state (tabs, connections, AI sessions)
  persists on disk and is restored on relaunch
- the Exa AI engine (exa-agent) is now a listed managed component: installed
  vs latest release of its real repo (Sheetaldharshan200/exa-engine — the
  repo installs actually pull from; the display repo was mislabeled), updates
  through the existing verify-or-refuse flow, and agent_restart bounces the
  sidecar after an engine update so nothing keeps running on the old binary
  (chat history is on disk and reloads)

CI
- refresh-runtime-components commit step gets the open-PR-only guard (the
  merged-PR match silently skipped raising the validated refresh PR)

* fix(marketplace): Codex review findings on any-version installs

- verified pins are safe again: market_install_run gains an explicit
  `requested` argument for the dropdown pick; the display version (catalog
  latest) can no longer silently override the pinned mcp-server/pyexasol
  pip specs on a plain Install
- update_component validates the version before it reaches a pip spec or a
  GitHub release-tag URL segment (shared valid_version_tag gate)
- the install manifest records only what actually ran: installer-resolved
  version, else the validated pick, else a validated display version — a
  malformed request can no longer persist a version execution ignored
- PyPI lists skip versions whose every file is yanked, not just empty ones
- sort_versions_desc ranks pre-releases (1.0.dev1, 2.0-rc1) below their
  final release; test added
- a finished install/update consumes its dropdown pick, so a stale hidden
  pick can never redirect a later 'Update to latest' click
- exa-agent joins MIRROR_IDS + the catalog items list (PR #149), so the
  rate-limit fallback also covers the engine

Marketplace.tsx remains >1000 lines (registry-driven card/update UI in one
place); versions.ts, assets.ts and StudioUpdateCard.tsx are extracted pure/
self-contained pieces, and IndependentComponents is the next extraction
candidate.

* fix(connections): deleting a connection actually removes it, and the row shows both actions

Removal
- deleting a profile now also unpublishes it from the shared ~/.exasol
  registry (entry + OS-keychain secret + legacy 0600 credential file) - every
  list re-imports registry entries that are "missing locally", so a delete
  that only touched Studio own store was resurrected on the very next refresh
- guarded for shared ids: two local profiles can derive the same registry id
  (the shared id ignores driver and case), so unpublish is skipped while any
  surviving profile still uses it
- the registry rewrite now carries the CLI defaultId through (previously the
  field was silently dropped on every read-merge-write) and clears it only
  when the removed entry WAS the default; pure without() core + tests

Sidebar
- saved-connection rows no longer swap Connect for the bin on hover: the
  wrapper carries the highlight, the connect pill stays visible (and lights
  up), and the trash fades into its own always-reserved slot - both actions
  side by side, keyboard-focusable, no overlay or layout shift

* fix(connections): self-heal the managed profile, prune internal AI-identity duplicates

The user removed a connection and ended up with the managed "Exasol Personal
(local)" profile gone (nothing recreated it while the DB was running) and
three identical internal "Local Exasol (AI read-only)" rows (stale MCP
identities, resurrected as user profiles by the shared-registry import).

- every profile listing now reconciles the managed local connection: while
  the managed database is installed, a missing profile is recreated from the
  deployment secrets (write-free otherwise) and the bootstrap status is
  repointed at the NEW id so the permanent local card stays wired
- internal STUDIO_MCP_* identities are not user connections: stale ones
  (not referenced by agent/mcp-identity.json) are pruned with their registry
  entries and secrets; publish skips them and cleans old registry copies;
  import refuses them; save_profile never publishes them, not even
  transiently on create
- prune is conservative: no marker or an unreadable one means UNKNOWN (prune
  nothing), and identities created in the last 10 minutes are never pruned —
  provisioning saves the profile before it rewrites the marker
- removing the managed row now says honestly that it will be re-created
  while the local database is installed (matched by notes marker OR managed
  name, since the backend recreates by endpoint)

* feat(marketplace): version lists for every installable item, including the in-Studio drivers

The drivers that run inside Studio (pyexasol, SQLAlchemy, JDBC, ODBC) took
the driver-runtime card branch, which never rendered the version dropdown —
exactly the cards where the user looked for versions first.

- the version picker is extracted and shared: it now shows on the
  runs-inside-Studio driver cards too; picking a version offers a
  "Download {v}" action (managed marketplace folder) while "Install & use
  here" keeps setting up the verified pinned runtime the SQL editor uses
- AI Lab lists Docker Hub tags (it is an image, not a pip package) and pulls
  the picked tag; its permission plan now tells the truth (docker pull)
- new "dockerhub" version source with bounded pagination (tags past page one
  no longer vanish) and a schema-surprise error instead of a fake empty list
- GitHub version lists are disk-cached for 1h under a process-wide lock with
  temp-file+rename writes (no lost entries or torn reads on concurrent
  dropdowns); stale cache is served over a rate-limit error; future cache
  timestamps are not treated as fresh
- a failed version fetch renders "could not load - reopen to retry" instead
  of the misleading "No published versions found"
- AI Lab tags validated against Docker tag grammar before reaching the CLI

* feat(marketplace): every driver installs from its native source - none are links anymore

All drivers are now independently installable, unpinned and unmanaged by
Studio, at any version, from the registry that actually publishes them:

- driver-ts: npm (@exasol/exasol-driver-ts) package tarball
- driver-go: Go module proxy zip
- exarrow-rs: crates.io crate
- driver-r: GitHub tag source package (repo exasol/r-exasol wired up)
- driver-odbc + driver-adonet: the OFFICIAL Exasol downloads portal via its
  machine-readable index (x-up.s3.amazonaws.com packages.json) - per-platform
  version lists (macOS universal ODBC included), downloads verified against
  the portal sha256 (verify-or-refuse); ADO.NET resolves its Windows package
  from any host, labeled honestly
- driver-websocket: current spec snapshot (protocol description + client
  implementations; a living spec with no releases, so no version list)
- new market_versions sources: npm / goproxy / crates / exasol-downloads;
  pure portal_artifacts platform mapper with tests
- independent-but-usable: "Use {v} in Studio" on the JDBC card points the SQL
  editor at a downloaded jar (same override as Drivers > Use custom JAR)

Codex review fixes:
- valid_version_tag rejects "." / ".." / leading-dot values (URL dot-segment
  normalization could have escaped the intended registry endpoint)
- uninstalling driver-jdbc clears a JDBC override that points into the
  deleted folder, so the Drivers UI never shows a jar that no longer exists

* feat(marketplace): one seamless install button, zero Get-only items left

One button
- the runs-inside-Studio driver cards collapse Download / Install / Use into
  ONE action: picking a version makes the same "Install {v} & use here"
  button download that version, wire it in where supported (the JDBC jar
  becomes the SQL editor driver) and set up the runtime
- Codex findings applied: a failed download keeps the pick, notifies and
  STOPS (never runs runtime setup as if it worked); the version menu freezes
  during a direct driver flow; a synchronous flow guard covers the
  runtime-only path against fast double-clicks

Everything installable
- terraform-provider: real binary install (its releases ship darwin/linux/
  windows zips - pickAsset routes the right one)
- dash-server: pip-installed from its GitHub tag tarball into a managed
  environment (not on PyPI), with a version dropdown
- more-functions: SQL function library snapshot download, ready to run in
  the SQL editor
- no catalog item is a Get-only link anymore; "Get" survives only as the
  no-build-for-this-platform fallback

* fix(marketplace): driver button just says Install

* feat(marketplace): zero-prerequisite installs, live driver-dropdown sync, honest installed cards

One install = usable, no dependencies
- every downloaded archive is extracted immediately (tar.gz/tgz/zip/crate;
  .taco/.jar/.whl/.nupkg stay whole for their tools), and extracted CLI
  binaries are linked onto Studio PATH (terminal + AI agent)
- extraction and linking are hardened per Codex review: symlink entries are
  never followed or linked (symlink_metadata walk + canonical containment
  under the unpack dir), a denylist keeps extracted binaries from shadowing
  common tools (git/docker/python/...), and a real managed binary in the bin
  dir is never replaced
- ODBC is one click end to end: portal download, checksum verified BEFORE
  extraction (nothing derived from an unverified file, not even a symlink),
  library wired straight into the pyodbc bridge by PATH (no OS registration;
  braces in the path escaped per ODBC grammar), runtime venv ensured;
  override-write failures now propagate instead of claiming success
- AI Lab provisions its own container engine via the shared
  ensure_engine_ready (start a stopped engine, or install Colima headlessly)
  instead of dead-ending on "install Docker first"; runs via spawn_blocking
  so minutes of brew/colima work never occupy async workers; engine_install
  gains the same 90s readiness wait as engine_start (delegation regression)

Synced UI
- the connection form driver dropdown and the Drivers table now refresh on
  market:done and studio:drivers-changed — an installed driver appears
  immediately, no remount needed
- installed cards keep the version dropdown and offer "Switch to {v}"; the
  "Up to date" pill only appears when a known latest CONFIRMS it, otherwise
  the card says "Installed · {version}" (repo-less portal drivers showed a
  false "Up to date" with no version at all)

* feat(marketplace): one card system - managed components live on their cards, no separate panel

- the "Components" panel on the Updates tab is gone (330 lines deleted):
  managed components (Personal, ExaPump, MCP Server, Exa Agent, Semantic
  Views) show installed state straight from list_components on their own
  cards and update IN PLACE via switchManaged (verify-or-refuse
  update_component; the DB engine backs up first; the AI engine restarts its
  sidecar after a switch)
- the Updates tab is now Studio own card + the same catalog cards filtered
  to those with an available update; the badge count computes directly from
  components + live upstream
- managed cards join multi-select: installs and updates are one gesture
  ("Install / update selected" - managed updates run sequentially under the
  DB maintenance lock, addon installs stay parallel)
- Exa Agent gets its own catalog card (bundled engine, versions from its
  release repo); Uninstall hides on managed cards (they have their own
  lifecycle - a marketplace-folder delete would be meaningless)
- Exasol Personal card: any-version switch via the managed path + a Back up
  button (both used to live on the removed panel)
- JDBC card gains "Custom JAR" (same override as the Drivers tab)
- the connection page Drivers tab reaches marketplace parity: per-driver
  any-version dropdown (same sources), picked versions install through the
  marketplace path first (the picked JDBC jar is wired in), then the runtime
  is ensured
- marketplace open no longer freezes: market_detect and community_status
  were SYNC Tauri commands running subprocess probes on the main thread -
  both now run via spawn_blocking; tab switches fade in

* feat(marketplace): industry-standard rate-limit handling + every card in the batch

Version lists never dead-end on the rate limit
- ETag conditional requests: refreshes send If-None-Match, and GitHub 304
  answers do NOT count against the unauthenticated 60/hr limit - once a list
  has been fetched once, keeping it fresh is free
- graceful ladder when the API is unavailable: fresh cache (1h) -> free ETag
  revalidation -> stale cache over any error -> the release cache known
  latest tag as a one-entry list (the dropdown still offers the newest
  version) -> only then the error message

Multi-select covers every installable card
- the runs-inside-Studio drivers (pyexasol, SQLAlchemy, JDBC, ODBC) were the
  only cards without a checkbox: they now join the batch, and their batch
  action is the full seamless runtime setup (skipped when already ready);
  addon installs stay parallel, managed updates stay sequential

* fix(marketplace): tab switches render smoothly

Switching Kits -> Catalog mounted ~28 cards in one synchronous render — made
WORSE by the key={nav} fade wrapper, which force-remounted the whole subtree
on every switch. Now:
- nav changes run in a React transition: the clicked tab highlights
  immediately and the heavy grid render is time-sliced (content dims slightly
  while pending)
- the fade runs imperatively on the ref — no remounts

* fix(docs): no white flash opening a guide

The docs open in an iframe, and an iframe paints WHITE until the page inside
applies its CSS — a hard flash in dark mode when clicking a guide from the
home page. The frame now stays invisible until onLoad (themed loader shown
over bg-editor), then fades in; a new URL (path change, theme toggle) hides
the fresh frame again until its own load fires.

* fix(marketplace): progressive card reveal - no tab-switch commit is big enough to hang

React transitions time-slice renders but the COMMIT of ~29 cards (each with
Radix dropdown triggers) was still one block, so Kits -> Catalog and every
other horizontal tab switch stalled. Cards now mount 12 at a time (first
chunk instantly, the rest 40ms apart), across the grouped catalog sections
and the flat tab grids alike.

* feat(ui): results tabs use the underline selection language; editor font is selectable

- the Results / Query Performance tabs drop the colored pill background for
  the same underline selection the Marketplace tabs use
- Settings > SQL Editor gains an "Editor font" selection (JetBrains Mono,
  SF Mono, Menlo, Monaco, Fira Code, Cascadia Code, Source Code Pro, IBM
  Plex Mono, Courier New) wired into Monaco with a clean fallback stack;
  applies live via the settings:changed broadcast — previously the family
  was hardcoded so no font selection could work

* fix(ui): brand loader during marketplace tab switches; settings sync hardened both ways

- while a tab transition is pending the THEMED brand loader overlays the
  dimmed content — the screen never looks frozen even on a slow mount
- settings sync can no longer go stale in either direction: the main window
  re-reads and re-applies settings whenever it regains focus (belt and
  suspenders against a missed settings:changed event), and the Settings
  window merges external changes live instead of holding its mount-time
  snapshot

* fix(marketplace): version lists work on rate-limited networks via releases.atom

On shared/corporate IPs the unauthenticated GitHub API 403s PERMANENTLY
(60/hr per IP, shared by everyone behind the NAT) — Exasol Personal always
showed "Couldn't load the version list" because no fetch ever succeeded
(no cache to serve, and its release-cache entry was null).

The releases.atom feed is a plain web endpoint with NO API rate limit: when
the API fails, tags now come live from the atom feed (parsed from entry ids
by a pure, tested function; deduped, validated) and are cached like an API
answer. Ladder: fresh cache -> API+ETag -> atom feed -> stale cache ->
release-cache latest -> error.

* docs: agentic architecture spec - honest gap analysis + phased plan

Maps the User -> Router -> Planner -> {Metadata, Semantic, Tools, DAG,
Policy, Verification, Observability, Evaluation} -> Answer reference
architecture against the real code, layer by layer, and specifies five
phases with designs, steps, Exasol assets and acceptance criteria:
P1 automated verification, P2 explicit plan object with preview,
P3 run observability (traces/tokens/latency + DB profile links),
P4 evaluation suites (seeded Nano in CI, results stored in Exasol),
P5 execution DAG (client core + in-DB offload via DISTRIBUTE BY /
Lua execute scripts / scheduler chains).

* feat(agent): P1 automated answer verification (agentic spec)

Every SQL-backed answer can now carry an independently reproduced stamp:
verified / mismatch / unverified — never silently trusted.

- verify.ts (pure, 7 tests): plans the LAST result-bearing read of a turn,
  refuses non-deterministic SQL, downgrades truncated results to row-count
  compare; value compare is order-insensitive with numeric tolerance
  (DECIMAL-as-string handled; null never equals 0 or empty string)
- db.verifyQuery: a DEDICATED throwaway session — reproduction is genuinely
  independent of the session the answer ran on
- desktop panel path (engine -> MCP gateway): run_query gains verify:true;
  the gateway reproduces the result (5s budget) and stamps the response;
  the tool description + scenario-router skill instruct the model to verify
  the answer-backing query only and to QUOTE the stamp - on mismatch both
  numbers are presented, never hidden. External MCP clients get this free.
- agent-core loop path: per-turn sqlRuns tracking, verification after the
  final message with a typed "verification" event + audit record; honest
  unverified outcomes on timeout/failure/nothing-verifiable
- frontend event union mirrors the new event

Spec: docs/agentic-architecture-spec.md P1.

* feat(agent): P2 explicit plans + P1 verification hardening (Codex findings)

P2 — explicit plan object (agentic spec)
- plan.ts (pure, 6 tests): schema validation (unique ids, known deps, no
  cycles, 12-step cap), computed write-approval requirement (write SQL via
  classifySql or write-shaped tools), enforced status transitions (a step
  cannot start before its dependsOn are done; unapproved write plans cannot
  start write steps; failed -> running is the explicit retry), progress
- plans-store.ts: one JSON per plan + a current pointer under dataDir/plans
- gateway: POST /gateway/plan | /plan/approve | /plan/step, GET /plan/current;
  MCP tools propose_plan / approve_plan / update_plan_step with descriptions
  that force the user-go-ahead gate for write plans (never self-approve)
- live plan card in the panel: plan updates push through the action queue
  server-side (informational — bypasses the app-control gate by design,
  and the bridge now always polls; CONTROL actions stay gated at enqueue);
  PlanCard renders goal, per-step status, awaiting-go-ahead badge
- dependsOn recorded from day one: it is the execution seam P5 runs on
- scenario-router: multi-step work proposes a plan first; single-step
  questions get zero ceremony

P1 hardening (Codex review findings applied)
- comparison is now lossless and type-honest: DECIMAL strings canonicalize
  textually (no float round-trip, safe beyond 2^53), VARCHAR "1" never
  verifies equal to numeric 1 (type drift IS a mismatch), JSON row
  serialization kills delimiter/sentinel collisions; test added
- db.verifyQuery self-limits: the 5s timeout CLOSES the throwaway session,
  so a slow verification never keeps running in the background (loop and
  gateway callers dropped their non-cancelling races)
- non-determinism denylist extended (GETDATE, UUID, SYS_GUID, ROWNUM, ROWID,
  NEXTVAL, CURRENT_USER/SESSION_USER/CURRENT_SCHEMA, SCOPE_USER)
- session.sqlRuns bounded (last 20 runs); run_sql_batch reads now feed
  verification too; every verification outcome is recorded (not just some)
  and survives session reload via a replay() case; planVerification runs
  inside the guard so it can never break finalize

* feat(agent): P3 run observability - trace spans, JSONL store, usage panel

Spec phase P3 (docs/agentic-architecture-spec.md): every turn, tool call and
gateway query now lands as a span in a local trace store, surfaced in a new
Settings > AI > Agent usage panel.

- trace.ts (pure, tested): TraceSpan, ToolSpanPairer (start/end by callId,
  200-cap, no fabricated durations), summarizeSpans (turns/tools/gateway,
  failures, tokens by provider and by day, avg tool latency)
- trace-store.ts (tested): daily JSONL under dataDir/traces, calendar-day
  30-day prune, bounded reads (20k spans), torn-line tolerant
- session.ts: static traceSink + per-session pairer hooked into emit();
  provisional streaming tool-starts are excluded from pairing so a
  pre-id chunk can never leak an open span
- loop.ts: turn span in finalize (tokens, provider, duration, steps) AND
  in the error/abort path (ok:false) so failed turns stay visible
- server.ts: TraceStore + sink wiring, gateway span on run_query for both
  success and failure, GET /v1/traces/summary + /v1/traces/recent
- desktop: agent.tracesSummary/tracesRecent + AgentUsage settings panel
  (day-window picker, provider table, recent activity; stale-response
  guarded refresh)

* feat(agent): P4 evaluation layer - golden suites, scoring core, STUDIO_EVALS

Spec phase P4 (docs/agentic-architecture-spec.md): a third eval tier that
measures ANSWER quality on a real database, beside the existing
deterministic (evals/run.ts) and live DB-free (evals/live.ts) tiers.

- src/evals.ts (pure, 18 tests): suite parsing with strict validation
  (empty values/patterns are rejected - they would match everything),
  numeric answer matching (separator-normalized on both sides, relative
  tolerance, adjacent numbers never merge), and per-kind scoring
  (value / rowcount / sql-contains / tool-used / refusal). Refusal
  distinguishes gate-held (write attempted, permission denied) from
  write-actually-ran - tool-start fires before the permission ask.
- evals/suites/core.eval.json: 10 seed cases over a self-contained
  EVAL_FIXTURE schema (metadata, describe, counts, aggregation, group-by,
  join, filter, rowcount, refusal, tool-check).
- evals/golden.ts (pnpm evals:golden): discovers local Exasol Personal
  deployments (or EXA_EVAL_DSN, redacted in errors), runs each question
  through the real turn loop with permission asks denied, scores from the
  turn evidence, records runs into STUDIO_EVALS.RUNS/RESULTS (escaped
  literals), exits non-zero below --min-pass (default 0.8). Provider
  registry reads the REAL config (keys/base URLs) while runTurn keeps a
  throwaway config so provider-limit writes never touch the user's.
- Agent evals System dashboard (system-dashboards.ts): pass-rate trend,
  failure counts by case, recent runs, latest failures.
- ci.yml: the deterministic tier now gates every PR.

Validated end to end against a local Personal DB: two runs recorded, the
threshold gate fires, and the refusal case passes via the held gate.

* feat(agent): P5 execution DAG - parallel plan steps, retry, compensation

Spec phase P5 (docs/agentic-architecture-spec.md), the last layer: an
approved plan's SQL steps now execute as a dependency DAG instead of
one-by-one.

- dag.ts (pure, tested): nextActions() decides everything from durable
  plan state - ready set from dependsOn, bounded concurrency (budget order:
  compensations, retries, fresh runs), exponential backoff from persisted
  attempts, skip cascade past hard failures, onFailure compensation
  triggering (comps also retry, and count against the budget), approval
  block, finished detection; normalizeAfterCrash() fails interrupted steps
  so resume just recomputes the ready set.
- plan.ts: PlanStep gains onFailure (validated: exists, not self, nothing
  may depend on a compensation step, comps have no deps and no own hook)
  and attempts (consumed on every running transition - durable, so retry
  budgets survive a crash); planProgress ignores untriggered comps.
- dag-executor.ts (tested against a scriptable stub DB): applies the
  actions, runs reads via db.query / writes via db.execute, persists
  through save() after every transition (best-effort - a failed disk write
  never re-classifies SQL that already ran), re-reads step state after
  backoff sleeps, refuses re-entry on an already-executing plan, refuses
  sql-less unfinished steps (pending compensations included) and
  multi-statement steps, and breaks instead of spinning on stalls.
- Gateway: POST /gateway/plan/execute (exposure/caps checks, the same
  writePolicy=deny guardrail run_sql honors - plan approval authorizes
  writes only under policy ask, plus a gateway trace span) and the
  execute_plan MCP tool; propose_plan schema gains onFailure.
- PlanCard: N-in-parallel badge, per-step after-deps hints, dimmed
  on-failure compensation steps; progress counting matches planProgress.
- scenario-router skill: when steps can fan out INSIDE the database,
  prefer DISTRIBUTE BY + SET UDFs / Lua pquery / scheduler AFTER chains -
  the SQL is the offload, the executor runs it as one step.

27 dedicated tests (12 DAG, 9 executor, 6 plan); 307 agent-core total.

* fix(agent): DAG steps run on isolated DB sessions - shared websocket hangs

Live acceptance smoke caught what the stub DB could not: parallel plan
steps were sharing the ONE pooled websocket driver, and the Exasol
protocol is strictly request-response per session - two concurrent
statements interleave frames and hang the executor forever.

- db.ts: queryIsolated/executeIsolated - one statement on its own
  throwaway session, closed in finally (the verifyQuery pattern).
- server.ts: /gateway/plan/execute passes the isolated pair as the
  executor's StepDb, so DAG parallelism maps to real parallel sessions.

Verified live against a local Personal DB: 6-step plan with two parallel
branches + join completed, the doomed branch retried once (attempts=2),
failed hard, ran its onFailure compensation, and the run reported
ok=false honestly - 1.1s end to end.

* docs(wiki): P3-P5 review findings - shared-websocket hang, refusal semantics, comp-step rules

* test(agent): end-to-end integration tier - the whole P1-P5 stack on the real gateway

pnpm smoke:gateway boots the REAL agent-core server in-process (temp data
dir, random port, bearer auth) and drives every layer through the actual
HTTP gateway the desktop app and MCP clients use - 16 checks, no model
needed, everything deterministic plumbing:

- P1: verify:true reproduces a result on an independent session; RANDOM()
  is honestly 'unverified'
- P2: a write plan requires approval and execution is BLOCKED before it
- P5: a diamond DAG completes both branches + join, the doomed branch
  retries once then fails hard, its onFailure compensation runs, the run
  reports ok=false honestly, and re-executing a settled plan is a clean
  no-op (resume semantics)
- Guardrail: writePolicy=deny refuses even an approved plan (asserted
  against an EXPLICIT plan id - the routes' current-plan fallback could
  otherwise false-pass), restored in finally
- P3: the activity lands as gateway spans in /v1/traces summary + recent

Exit paths are safe: a mid-run crash drops the smoke schemas over a
direct isolated session and removes the temp dir (it holds the gateway
token while alive). Local-DB discovery + DSN parsing moved to
evals/local-db.ts, shared with evals/golden.ts (behavior identical,
re-probed). Spec header now carries the shipped status, the per-layer
state table, and the five-tier verification matrix; graphify graph
refreshed for the P3-P5 modules.

* feat(agent): self-maintaining semantic layer - auto validate + surface refresh

The Semantic Views layer now keeps itself current: EVERY database change,
from EVERY surface, triggers a debounced semantic pass on that connection.

- semantic.ts (pure, tested): classifySemanticImpact - schema DDL
  (CREATE/ALTER/DROP/RENAME) vs data loads (INSERT/UPDATE/DELETE/MERGE/
  IMPORT/TRUNCATE) vs none; leading comments stripped so a commented
  migration still classifies; EXECUTE SCRIPT never self-triggers.
- semantic-sync.ts (tested with a scriptable framework stub): per-
  connection debounce (an import burst = ONE pass), serialized passes,
  silent no-op without the framework. A pass runs
  SEMANTIC_ADMIN.VALIDATE_MODEL for every active model and - for SCHEMA
  impact only - SEMANTIC_ADMIN.REFRESH_SEMANTIC_SURFACE for models that
  are ALREADY PUBLISHED and validated (REFRESH wraps PUBLISH_MODEL;
  drafts are never auto-published). Issues come from
  SEMANTIC_CATALOG.CURRENT_VALIDATION_ISSUES, errors ranked first.
- db.ts onWrite: the single choke point - fired after every SUCCESSFUL
  write in execute/executeIsolated, and for bulkLoad only AFTER COMMIT
  (observers never see statements a rollback can retract).
- server.ts: wires the hook; connect-time VALIDATE-ONLY pass catches
  changes made outside the agent (exapump, other clients); results land
  as a trace span + the same studio:notice toast the Rust path shows.
- tools.ts: semantic_models tool (models + status + published schema +
  current issues) - the spec paragraph-6 discovery tool; scenario-router
  now says to call it first and prefer published measures over ad-hoc SQL.
- Rust semantic_sync.rs/query.rs: Studio-editor statements get the same
  treatment - schema changes validate AND refresh published surfaces,
  bulk loads (IMPORT/MERGE/TRUNCATE) validate only.

Codex findings fixed pre-commit: pre-commit bulkLoad notifications,
Rust refreshing surfaces on data loads, comment-blind classification,
and republish-on-every-connect churn.

* feat(agent): semantic coverage for new datasets - detect, notify, draft

Loading a NEW dataset previously left it semantically invisible: the sync
validated existing models but nothing said 'this schema has no model at
all'. Now it does, and drafting one is a governed one-ask flow.

- semantic.ts: uncoveredSchemas (pure, tested) - user schemas with tables
  that no ACTIVE model binds; system/framework/fixture schemas excluded by
  literal name (published schemas excluded dynamically by NAME from
  SYS_SEMANTIC.MODELS, never by prefix guess); catalog casing preserved
  for quoted identifiers, matching case-insensitive. classifySemanticImpact:
  EXECUTE SCRIPT is now schema-impacting (Lua pquery can run DDL - the
  in-DB offload path - and drafting a model SHOULD trigger a validation
  pass); only the sync's own VALIDATE_MODEL/REFRESH_SEMANTIC_SURFACE calls
  are exempt, so it can never self-trigger.
- semantic-sync.ts: every pass reports uncoveredSchemas; trace meta and a
  new info toast ('Dataset X without a semantic model - ask the assistant
  to draft one').
- tools.ts: semantic_models returns schemasWithoutModel; NEW semantic_admin
  tool (one SEMANTIC_ADMIN script by name via CALL_ADMIN_JSON - an AUDITED
  read-only allowlist runs freely, everything else asks the user with the
  FULL untruncated payload) and semantic_apply_definition (dry-run default,
  commit asks with the full definition). Drafts stay DRAFT - publishing
  needs the user's explicit ask.
- dag-executor.ts: EXECUTE SCRIPT steps route through the query path - the
  driver's execute() throws on a script's result table AFTER its side
  effects committed, which double-ran scripts on retry (found live:
  duplicate model). Gateway wiring notifies the semantic sync for script
  steps.
- scenario-router: the new-dataset drafting discipline.

Codex findings fixed pre-commit: truncated permission previews, the
write-observer gap for script steps, over-broad EXECUTE routing, the
prefix-based allowlist, uppercasing away quoted identifiers, and prefix
exclusions hiding real user datasets.

Live-verified twice against Exasol + Semantic Views 0.2 (8/8): load
dataset -> reported uncovered -> draft via approved plan (CREATE_MODEL +
ADD_ENTITY through CALL_ADMIN_JSON) -> stays DRAFT -> next pass validates
the new model and coverage closes.

* feat(agent): semantic-layer parity on the gateway surface

The visible AI panel (MCP gateway) previously had NO semantic tools - the
loop/CLI surface got them all. Both surfaces now share one implementation:

- semantic.ts: SEMANTIC_READONLY_SCRIPTS - the single audited allowlist
  both surfaces gate on. VALIDATE_MODEL is deliberately excluded: it
  records validation runs (a catalog write) and the automatic sync is the
  validation authority; manual validation goes through the approval paths.
- semantic-sync.ts: semanticOverview(query, id) - one snapshot (models,
  open issues, schemasWithoutModel) shared by the loop's semantic_models
  tool (refactored, duplicated SQL removed) and the new gateway route.
- server.ts: resolveGatewayDb (name/id + exposure + SQL-cap gates);
  GET /gateway/semantic/models; POST /gateway/semantic runs ONE read-only
  SEMANTIC_ADMIN script via CALL_ADMIN_JSON on an isolated session -
  catalog mutations get a 403 pointing at the approved-plan path, so the
  gateway's write gate stays singular.
- mcp-gateway.ts: semantic_models + semantic_call tools with the
  discovery-first / draft-via-approved-plan discipline.
- loop.ts: progressive disclosure now surfaces ALL semantic tools when the
  layer is ready (discovery and authoring included), not just the compilers.
- evals/golden.ts: when the target DB has the framework, the runner writes
  the same capability marker the desktop uses, unlocking loop semantic
  tools; suite cases prefixed 'semantic-' are SKIPPED (exit 0 with an
  honest message) where the framework is absent. New 'semantic-first'
  golden case: the spec's 'used the semantic layer when available'
  expectation.

Codex findings fixed pre-commit: VALIDATE_MODEL on the no-approval
conduit, semantic_models missing from progressive disclosure, and the
confusing 'no eval cases found' when a semantic case is skipped.

Live-verified 5/5 against Exasol + Semantic Views 0.2 (models snapshot,
coverage, COMPILE_SQL conduit, mutation refusal, name validation); the
16-check integration smoke stays green.

* docs(wiki): semantic auto-sync design rules and live-run findings

* fix(market): Semantic Views install survives the GitHub API rate limit

The Marketplace install of Semantic Views (and the agent-skills source
fetch - both callers of upstream::fetch_source_tree) failed with 403 on
shared corporate NATs: the source tarball came exclusively from
api.github.com, whose unauthenticated per-IP limit is permanently
exhausted there. Same root cause as the version-list failures fixed
earlier; same remedy:

- try the API tarball first (exact behavior as before when it works)
- on failure, resolve the default branch's commit sha from the repo's
  commits/HEAD.atom feed (not rate-limited), then download from
  codeload.github.com/<repo>/tar.gz/<sha> (also not rate-limited)
- revisions normalized to the 7-char short sha so both paths record the
  same value for the same commit (readiness markers must not churn)
- both fetch errors carried in the failure message when everything is down

sha_from_commits_atom is pure and unit-tested; verified live: the atom
feed resolves real shas for exasol-labs/exasol-semantic-views from this
network, where the API tarball returns 403.

* fix(market): Semantic Views readiness probe is Studio's own, not upstream's

The install got past the fetch (previous fix) and then failed with
'readiness probe exited with code 2': Studio ran tools/probe_ready.py
from the fetched source tree, and upstream no longer ships that file -
python exits 2 on a missing script, so every install failed AFTER the
framework was already installed.

The probe is Studio's contract, so it now lives IN Studio as an embedded
script (the probe_credentials precedent in this file), run via python -c:
exit 0 = framework + complete example, 1 = framework missing, 3 =
framework only (clean), 4 = SALES/MART objects incomplete or user-owned.
Every SQL in it verified live against a real installation.

Also fixes a latent reinstall loop: Studio's own install is CLEAN (no
example), which probes as 3 - but the previously-ready fast path accepted
only 0, so each ensure would have reconciled (reinstalled) forever. 0 and
3 are both READY there now.

* fix(marketplace): tab-switch loader shows the brand mark, not loading text

The Kits -> Catalog transition overlay used the draw-from-zero BrandLoader
(3.4s loop, stroke complete only at ~1.1s) with a Loading label at 40px:
in a short pending window the mark was near-invisible, so the user saw
plain loading TEXT.

BrandLoader gains an "instant" variant - the filled Exasol X from the
FIRST frame, kept alive by the pulsing glow, no label needed. The
marketplace nav overlay uses it at 56px; boot screens and long waits keep
the slow draw loop, which is right for them.

* fix(assistant): drop the redundant Save button from the SQL-grants shield

Closing the shield popover already commits staged toggles (dismiss IS the
save - a change is never silently lost), so the Save button in the sticky
header was a second path to the same action. Removed; a quiet "applies on
close" hint appears in its place while there are staged changes.

* fix(assistant): hide the engine synthetic tool echo from user messages

When the engine tools read a file, opencode appends SYNTHETIC parts to
the USER message ("Called the Read tool with the following input: {...}"
followed by the entire file body) - model-only context. The panel
rendered those parts as if the user typed them, and editing the message
seeded the input box with all of it.

Synthetic parts always TRAIL the typed text, so stripMachineContext now
also cuts everything from the first tool-echo marker on - which covers
the bubble, Copy, and the Markdown export in one place (they all strip
through it). The edit composer additionally rewrites its runtime-seeded
text on mount via the message-scoped composer client, so editing shows
only what the user actually wrote. Plain text that merely mentions a
tool is untouched (tested).

* fix(assistant): the panel agent can draft semantic models and resume its plan

The live transcript showed the panel agent proposing a TPCH semantic-model
plan, then giving up ("requires specific admin script syntax") and, when
the user said to finish the todo list, not even seeing its own open plan.

- New current_plan gateway tool: "continue / do it / finish the todo list"
  now resolves to the OPEN plan and its per-step status (the route existed;
  no tool exposed it).
- The drafting recipe is spelled out where the agent looks (semantic_models
  tool description + scenario-router), including the one thing that makes
  guessing unnecessary: SEMANTIC_CATALOG.ADMIN_SCRIPT_PARAMETERS lists
  every admin script signature and call template - a plain SELECT away.
- semantic_admin (loop tool) points at the same signatures view.

Proven end to end on the live app sidecar: the TPCH draft model was built
through five approved plans (CREATE_MODEL, 7 entities with grains, 6
relationships with unique keys and key mappings, semantic object, 4
dimensions, facts + total_revenue metric via APPLY_SEMANTIC_DEFINITION,
VALIDATE_MODEL) - zero validation issues, TPCH no longer uncovered, DRAFT
status preserved (publishing stays a user decision).

* fix(editor): EXECUTE SCRIPT results reach the grid instead of "0 rows affected"

Running EXECUTE SCRIPT ... RETURNS TABLE in the SQL editor showed
"0 rows affected": is_result_set_statement only whitelisted the
SELECT family, so scripts went down the rowcount path and their result
table was silently discarded - the same bug class fixed in the DAG
executor earlier. EXECUTE now routes through the fetch path, which is
safe for BOTH script shapes (the driver streams a plain script rowcount
result as zero rows - verified in sqlx-exasol QueryResultStream).

Editor-run scripts also trigger the background semantic revalidation now
(is_semantic_impacting_script - Lua pquery can run DDL, SEMANTIC_ADMIN
scripts change the catalog itself), matching the agent-side rule, with
the sync own VALIDATE/REFRESH calls exempt so it can never self-trigger.

* fix(editor): complete + comment-proof routing of output-producing statements

Follow-up to the EXECUTE SCRIPT fix: the user asked which OTHER statement
shapes lose their output. Two were still misrouted to the rowcount path:
parenthesized queries ("(SELECT 1) UNION ...", no leading keyword) and
any query behind a same-line block comment ("/* hint */ SELECT ...",
which the crude line-based comment scan could not see past).

A full fetch_many unification (ask the database instead of classifying)
was BUILT AND REVERTED on Codex review: sqlx-exasol implements the only
both-kinds API via ExecuteBatch, whose own semicolon splitter would shred
CREATE SCRIPT bodies, and multi-result streams would misreport counts.
With single-statement routing required, a keyword head is the correct
mechanism - and the set is CLOSED under Exasol grammar (documented on the
classifier): SELECT / WITH / VALUES / parenthesized query / DESCRIBE /
DESC / EXPLAIN / EXECUTE SCRIPT.

- strip_leading_comments: iterative -- and /* */ stripping shared by the
  router and the semantic classifiers (replaces the line-based scan)
- "(" prefix routes to the result-set path
- is_semantic_impacting_script: comment-proof, and the sync self-call
  exemption now matches only DIRECT call targets - a wrapper script that
  merely mentions VALIDATE_MODEL in an argument still triggers a pass

* docs(graph): refresh graphify for the P3-P5 + semantic-layer era

* feat(agent): close the last two P4 eval deferrals (import→query, clarify)

The spec listed both as deferred; both now exist as real golden cases.

- clarify expectation kind: the question names an undefined business term
  ("high-value orders"), so the only correct move is to ask for the
  definition. Scoring needs a request for the MISSING thing plus a request
  form - a "?" is not what makes it a request, and an offer made after
  answering ("...would you like me to export them?") is not one either -
  and an invented definition ("assuming high value means over 500") vetoes
  the pass outright.
- attachments + approveWrites on a case: the runner hands suite files to
  the turn, and a load→query case opts out of the blanket permission denial
  the refusal case depends on. parseSuite REFUSES approveWrites together
  with refusal/clarify: those assert nothing was written, but import_csv
  writes without emitting SQL, so the combination is unsound as well as
  contradictory.
- import-then-query asserts the query half too (run_sql + the table name),
  not just the import, and creates its own schema so --case works on a
  fresh database.
- Attachment fixtures are validated as fixtures: plain filenames only (the
  name reaches the model-facing note and the document store), images must
  be data: URLs, 256 KB cap.

Codex review found 3 real bugs (weak clarify detector, the unsound
approveWrites+refusal combination, order-coupled setup) and 2 minors
(attachment hardening, the unasserted query half) - all fixed here, each
with its example string as a regression test. 333 agent-core tests green.

Also refreshes the .ua file inventory (a full graph rebuild is still
pending - it needs a dedicated run).

* fix(drivers,runtime): stop drivers running as other drivers; unblock Personal 2.3

Investigation of the driver matrix and Exasol Personal 2.3-rc found two live
defects, both fixed here. The full plan is docs/platform-drivers-spec.md.

Drivers — a selected driver could silently run as a different one.
driver_runtime() declares node/go/r/dotnet runtimes that no bridge
implements, and the Python bridge's else-branch runs pyexasol: picking
"Exasol TS driver" opened a pyexasol connection and said nothing. Same class
as the "0 rows affected" script bug — a wrong answer, not a missing feature.
execute_via_driver now refuses an unimplemented driver by name, and
driver_status derives supported/hint from that SAME authority so the picker
can never offer what execution refuses. That also catches two the UI
previously claimed outright: sqlalchemy (ran plain pyexasol) and exarrow-rs
(ran the in-process sqlx driver).

Runtime — 2.3 would have broken local setup entirely. 2.3 makes local host
preparation (where it installs/prepares Podman) FAIL when it cannot prompt,
and Studio always drives the launcher non-interactively, so install/start
must pass --auto-approve. That flag does not exist before 2.3, where an
unknown flag is itself an error, so the launcher is asked what it accepts
via the existing `install --help` probe rather than assuming a version. The
decision is split from the subprocess and unit-tested.

121 Rust tests green.

* feat(drivers): TS driver runs natively; SQLAlchemy runs as SQLAlchemy

Two of the listed drivers now really execute, neither asking the user to
install anything.

TS driver — natively, on what the app already ships. Studio bundles a Node
runtime AND @exasol/exasol-driver-ts, so the new driver-bridge.cjs needs no
new runtime and no sidecar coupling: it speaks the same stdin/stdout JSON
contract as the Python bridge, and uses the driver's RAW mode so the
protocol itself reports resultSet vs rowCount — no guessing from statement
text, the same lesson as the editor routing fix.

SQLAlchemy — through its own dialect (exa+websocket) rather than the old
fallback to plain pyexasol, which was a different driver wearing its name.
exec_driver_sql keeps ':' in SQL from being read as a bind parameter, and
AUTOCOMMIT keeps DDL/DML from rolling back when the connection closes.

Live testing against a real database caught two defects that unit tests
could not have:
- the driver rejects with PLAIN OBJECTS, not Errors, so every failure
  rendered as "[object Object]" and lost the database's message
- the raw envelope's status/exception were ignored, so a SELECT on a
  missing table reported SUCCESS with 0 rows

Both fixed and covered by tests; mapRawResult is now a pure function so
every branch (error status, bare exception, missing results, resultSet,
rowCount, truncation) is unit-tested. Verified live: typed result sets with
NULL preserved, INSERT reporting 3 affected rows, maxRows truncation
flagged, and a failing statement returning "42000: object ... not found".

339 agent-core tests green. NOTE: cargo test could not run — the local
toolchain is blocked on an unaccepted Xcode license (sudo xcodebuild
-license accept); the Rust side passes cargo check and its logic is
unchanged apart from routing.

* Run exarrow, Go and R as real drivers; refuse ADO.NET for the real reason

Three of the drivers Studio listed could not actually run, and one was
listed as if it were only a matter of time.

exarrow-rs now runs IN-PROCESS. It is a Rust crate compiled into Studio,
so picking it installs nothing, and it returns Apache Arrow batches -
which is the point of choosing it: you see what an ADBC/pandas/Polars
client gets back. exarrow_exec.rs keeps the Arrow -> grid conversion
pure and tested: ints stay numbers, scaled DECIMALs stay exact text
(through f64 they would round), dates and timestamps render exactly like
the native driver, NULL is decided from the validity bitmap first, and
`truncated` is set only when a row was really dropped. Two live tests
prove it against a real database, including that a failing statement is
an ERROR and not a silent zero-row success.

Linking exarrow brings a second rustls crypto provider into the binary,
and with two compiled in rustls' automatic process-level lookup panics
instead of choosing. Studio installs ring as the default at startup -
ring, deliberately: sqlx names it explicitly and reqwest consults the
process default before falling back to it, so every existing TLS path
is untouched and only exarrow's own connections are newly resolved.

Go runs natively too: a prebuilt exasol-bridge-go binary, built by
scripts/build-driver-bridges.sh and shipped as a bundle resource (the
bridges directory is committed so the build cannot fail on a missing
resource; release CI builds the real binary per target). Live-proven -
typed result sets, exact decimals, real NULLs, real database errors, and
the batch stops at the first failure.

R is detected, not bundled: it is a large runtime that hard-codes its
own install paths, and the official `exasol` package compiles against
the Exasol ODBC driver. Studio ships the bridge script, finds Rscript,
installs the package into its OWN R library, and refuses clearly when R
is absent.

ADO.NET stays refused, and now says why: Exasol publishes it only as a
Windows MSI - the downloads index lists Windows alone and there is no
NuGet package - so it can never run here. A refusal that names the real
reason beats one that implies a backlog item.

Bridges no longer guess which statements return rows: Studio computes it
once with the same classifier the native path uses and sends it, so no
statement is ever run twice to find out. The Python bridge moved next to
the Go and R ones as real, readable Python, which also brings
driver_exec.rs back under 1,000 lines.

* Record the driver outcomes in the platform spec

Workstream A is done, so the spec should say what is true now instead of
what was true when it was written: which drivers execute through their
own runtime, which is detected rather than bundled and why, and which is
closed as impossible off Windows. The A4-A6 decision the spec was
holding open is settled and written down with its reasoning; the only
question left there is whether to build a Windows-only ADO.NET bridge,
which needs a Windows machine with the provider installed.

A9 is marked partial on purpose: exarrow's live checks are automated and
opt-in, TS and Go were proven by hand. Claiming a tier that does not
exist yet would be the same kind of lie the gate work removed.

* Send every R statement through dbSendQuery

r-exasol exports dbSendQuery and dbGetRowsAffected but not dbExecute or
dbSendStatement, so DBI's dbExecute would dispatch to a method that does
not exist: every INSERT, UPDATE and DDL through the R driver would have
failed. One path now serves both kinds — fetch rows, or read the
affected count off the same result.

The result handle is also released per statement. The `on.exit` it used
before sits in a loop at script top level, so it registers on the global
frame and nothing is freed until the script ends.

* Finish the R driver and prove every bridge against a real database

The R driver ran for the first time against a live database, and three
things only that could expose turned up.

The `exasol` package prints progress to STDOUT — "EXASOL driver loaded",
"Using temporary schema: TEMP_…_CREATED_BY_R". Studio parses stdout as
one JSON document, so a single s…

Exasol Studio v2026.8.1

Choose a tag to compare

@github-actions github-actions released this 22 Sep 07:35
4571ae7
Exasol Studio 2026.8.1

Exasol Studio v2026.8.0

Choose a tag to compare

@github-actions github-actions released this 22 Sep 06:17
d173979
Exasol Studio 2026.8.0

Exasol Studio v2026.7.1

Choose a tag to compare

@github-actions github-actions released this 02 Sep 06:47
889b9b2
Release 2026.7.1 (#133)

Exasol Studio v2026.7.0

Choose a tag to compare

@github-actions github-actions released this 02 Sep 06:04
af09c0c
Release 2026.7.0 (#130)

Exasol Studio v2026.6.1

Choose a tag to compare

@github-actions github-actions released this 01 Sep 09:36
ad9363a
Release 2026.6.1 (#89)

Since 2026.6.0: web marketplace real installs, engine-hosted MCP
gateway + AI clients in the browser, AI Personalization settings tab,
universal search (⌘K) with fuzzy relevance, notebook cell pinning with
apply-and-run, export multi-select, in-app Docs tab with theme sync,
quiet compaction, friendly provider errors, instant connect/disconnect,
zero native dropdowns. Engine baseline v2026.1.83.

Exasol Studio v2026.6.0

Choose a tag to compare

@github-actions github-actions released this 31 Aug 09:16
6397af7
Release 2026.6.0 (#72)

* Release 2026.6.0

The notebook artifact hub release: visual chart picker, per-cell
database choice, System dashboards in the notebook, exa prompt seam,
Dashboards surface retired, exa-engine submodule. Spec archived.

* In-app Docs tab, guide deep links, session-delete sync

- New Docs tab renders the served documentation INSIDE the app (iframe
  of the engine's /docs/studio; web build uses same-origin). Header has
  "Open in browser" and an explicit "exa docs ↗" that always opens the
  engine notebook externally. Title-bar Docs button targets the tab.
- Welcome "Guides" entries deep-link to their exact docs pages
  (getting-started, connections, workbench/files, workbench/sql-editor,
  connections/drivers) in the Docs tab instead of one generic surface.
- Deleting a chat now syncs everywhere: the panel dispatches
  exa:session-deleted, the open thread starts fresh when it was showing
  the deleted session, and the persisted active session is validated
  against the engine's list on every refresh (a chat deleted elsewhere
  or a stale localStorage entry can't reopen).

Exasol Studio v2026.5.0

Choose a tag to compare

@github-actions github-actions released this 31 Aug 06:51
Tauri 2 + React 19 desktop and web Exasol IDE with the exa AI engine:
SQL workbench, schema browser, dashboards, marketplace, assistant with
personas and structured questions, connection management, and docs.
History starts fresh at this release.

json-tables (prebuilt by CI)

Pre-release

Choose a tag to compare

@github-actions github-actions released this 14 Jul 09:42

Prebuilt JSON Tables ingest engine (all distributions) + Python wheel,
built once from exasol-labs/exasol-json-tables so users need no Rust/cargo.
version=c123439080e8