Commit Graph
8388 Commits
Author SHA1 Message Date
hugocasaandClaude Opus 4.8 60cf287c11 fix(git-sync): address PR review findings
- webhook_secret: redact from the settings API response and Debug output (still
  persisted encrypted); it's a server-only HMAC key the UI never needs.
- poller: honor each repo's effective poll interval (relaxed ~10 min when a
  webhook is live) instead of probing every ~60s tick.
- settings save: roll back a just-created webhook if the settings transaction
  doesn't commit, so a failed save can't orphan a hook.
- auto-pull head check: fail SSH remotes with an actionable message (background
  polling has no SSH identity) instead of a confusing ls-remote error.
- deploy/PR check summary: a pull result carrying neither changes nor a settings
  diff now falls back to the unsummarized path instead of a false "in sync".
- UI: reset isGithubApp on resource change / failed fetch so webhook + fork
  controls can't show for the wrong repo.
- tests: cover parse_git_sync_changes and format_change_list edge cases.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PP5gBSPfo1YtkL1sWVAjJm
2026-07-01 18:53:53 +02:00
hugocasa 60570ef9ef Merge remote-tracking branch 'origin/main' into explore-git-sync-improvements
# Conflicts:
#	backend/ee-repo-ref.txt
2026-07-01 16:40:12 +02:00
hugocasaandClaude Opus 4.8 ab181519c3 feat(git-sync): fork auto-sync (phase 5) + live deploy check (phase 6)
Phase 5 — fork auto-sync configured at the parent (replaces the *-to-forks
GitHub Actions):
- Add fork_open_prs + fork_pull_sync to GitRepositorySettings (openapi + UI).
- UI: two "Forks of this workspace" toggles in the repo card, gated on
  app-backed and not-a-fork; serialize the flags on save.
- On fork creation, strip the inherited auto_pull block (and fork_* flags) from
  the copied git_sync repo: a fork must not carry the parent's webhook id (it
  would delete the parent's hook on disable) or self-poll on top of the parent's
  fan-out. Push-direction config + installation are still inherited unchanged.

Phase 6 — live deploy status check on the commit (Cloudflare-style): an
in-progress "Windmill" check on the head commit that flips to "Deployed N
changes"; completion handled by the generalized git-sync check hook.

Bump EE ref for the phase 5-6 EE implementation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PP5gBSPfo1YtkL1sWVAjJm
2026-07-01 16:39:38 +02:00
Ruben FiszelandClaude Opus 4.8 289017bcb2 feat(frontend): add zoom and download to Mermaid graphs (#9859)
* feat(frontend): add zoom and download to Mermaid graphs

Mermaid diagrams in the AI chat could only be viewed inline with
horizontal scroll. Add a download-as-SVG button and an expand button
that opens a fullscreen modal with pan/drag and zoom (mouse wheel plus
in/out/reset controls), reusing the existing `panzoom` dependency.

Fixes WIN-2117

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(frontend): guard panzoom action against close-before-import race

Address review nit: if the modal closes before the dynamic
import('panzoom') resolves, destroy() ran while instance was still
undefined (disposing nothing) and the late .then() built a leaked
panzoom on a detached node. A disposed flag makes the cleanup airtight.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 14:05:30 +02:00
Ruben Fiszelandrubenfiszel cfcc0b9453 chore(main): release 1.744.0 (#9839)
* chore(main): release 1.744.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2026-07-01 11:47:34 +02:00
Ruben FiszelandClaude Opus 4.8 74f579e6d9 feat(pipeline): local development for data pipelines (CLI --local + pipeline dev preview) (#9840)
* feat(pipeline): local development for data pipelines (CLI --local + pipeline dev preview)

Add the local edit→preview→run loop for data pipelines (folders of `// pipeline`
scripts), the analog of `wmill dev` / `wmill app dev`, usable from a code editor
or an agentic loop — without deploying.

No backend changes: full body inference comes from the same wasm the frontend
uses (windmill-parser-wasm-asset), which returns assets + pipeline annotations in
one call; local runs reuse runScriptPreview with _wmill_skip_asset_dispatch.

- localGraph.ts: wasm-backed working-tree → asset-graph builder (the enabler)
- pipeline show/run --local; new pipeline docs (PIPELINE.md/AGENTS.md) subcommand
- pipeline dev watcher + /pipeline_dev page (PipelineDevView) rendering the same
  PipelineGraphEditor from the pushed local graph, run via preview
- cascadeRun.ts: reusable run primitives extracted from the route page
- regenerated CLI agent docs

See docs/pipeline-local-dev.md for the full design, test steps, and handoff TODOs.
The live `pipeline dev` browser preview is implemented but not yet stack-verified.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(pipeline): improve local dev preview (run, activity, responsive)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(pipeline): dev-preview args, multi-root run, ws auto-reconnect

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(pipeline): connect managed-materialize producer in local dev graph

The CLI pinned windmill-parser-wasm-asset ^1.728.1, which predates managed-materialize support (added in 1.733.1); the frontend already pins 1.740.0. The CLI's wasm therefore never emitted `// materialize`, so the producer had no output edge and showed disconnected from its `// on` consumers. Bump the CLI to 1.740.0 (matching the frontend) and translate the parsed materialize target into the producer's write edge + materialize_target, mirroring frontend resolveGraph.ts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(pipeline): harden local-dev CLI (bare-.sql crash, defaultTs, docs clobber)

Review fixes, complementary to the dev-preview/materialize/multi-root work already
on the branch (none overlap those commits):

- localGraph: a bare `.sql` (no dialect) made inferContentTypeFromFilePath throw and
  abort the whole graph build — and wedge `pipeline dev` at startup. Skip the
  unclassifiable file instead. Also map `bunnative` → parse_assets_ts and add
  ruby/rlang/nu/powershell to the `#`-comment fallback.
- show/run/docs/dev: thread the resolved `wmill.yaml` defaultTs into the graph
  builder so `.ts` infers under the workspace's runtime (bun vs deno) instead of
  always bun — `opts.defaultTs` was always undefined (no such CLI flag).
- dev: wrap the startup graph build so a half-written file can't abort the watcher.
- docs: don't clobber a user-authored AGENTS.md/CLAUDE.md — only (over)write the
  pointer when absent or already a generated `@PIPELINE.md` pointer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(pipeline): bind dev WS to loopback + local-graph regression tests

- pipeline dev WS broadcast the folder's full script source (scripts[].content + temp_script_refs) unauthenticated on 0.0.0.0:3201 — bind 127.0.0.1 so it's not LAN-reachable (webview localhost + SSH/devbox port-forward still work).

- Add regression tests for the just-landed local-graph fixes: bare .sql is skipped (was a build/dev-startup crash), defaultTs threads into .ts runtime inference (bun vs deno), and #-comment languages (ruby) use the # annotation fallback.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(pipeline): --frontend flag for pipeline dev page origin

wmill pipeline dev opens <remote>/pipeline_dev, but that route only exists in this build's frontend, so it 404s against a remote whose deployed frontend predates it. --frontend <origin> points the page at a locally-run frontend (REMOTE=<remote> npm run dev) while the API/token still target the remote — enabling the live preview against a real backend before the PR is deployed. No behavior change when omitted. Regenerated CLI agent docs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(pipeline): WS session token + details-pane live-reload refresh

Addresses CI review (Codex/Pi/Claude):

- dev WS: a browser tab could open ws://localhost:<port>/ws and receive the folder's full source (browsers don't enforce same-origin on WS, loopback bind alone doesn't help). Gate the upgrade on an unguessable per-session token carried in the dev-page URL (verifyClient → 401 without it). Verified: no-token/bad-token connections get 401 with no bundle.

- details pane: scriptRes keyed on [workspace, selection, draftScript] didn't re-run on a pipeline dev live-reload (same selection), so the open pane showed stale source. Thread a localScriptsVersion (the pushed bundle) into the key. Verified: editing a selected node's file updates the pane source without reselect.

- docs/pipeline-local-dev.md: refresh the stale 'not yet exercised' status + done TODOs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(pipeline): emit volume: annotation assets in local dev graph

Addresses CI review (Codex P1 / Pi P1): the wasm body parser doesn't surface `// volume: <name>` annotations — the frontend (infer.ts:parseVolumeAnnotations) and backend (asset_inference.rs) parse them separately and merge as rw volume assets. localGraph didn't, so a `# volume: cache` producer had no write edge and showed disconnected from its `// on volume://cache` consumer (and pipeline run --local wouldn't schedule downstream). Mirror the leading-comment-block scan (SQL excluded, matching both reference parsers) and merge into inferScriptAssets. Regression test added; verified producer -> volume://cache -> consumer connects.

Also (Codex P2): docs/pipeline-local-dev.md manual browser URL omitted the new ws_token param — without it the WS upgrade is rejected and the page sits disconnected. Doc now says to copy the URL the CLI prints (carries wm_token + ws_token) and recommends --frontend.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(pipeline): runAll excludes event roots + review polish

Addresses CI review (Codex P1, Claude P2/P3):

- pipeline run runAll: derive the whole-pipeline selection from validStarts + descendants instead of all runnables, so an unqualified 'pipeline run <folder>' no longer fires event-trigger roots (kafka/mqtt/…) with empty args/side effects. Verified: a kafka root is excluded from the plan.

- cascadeRun.ts runBoundedCascade: use buildLineageDownstreamMap (read-aware) so a pure-reader runs after its producer, and return cyclic — parity with the route page's bounded run (the file is meant to be THE shared correct primitive).

- PipelineGraphEditor: storedRightPaneSize starts at 0 so the orientation-aware default (55% stacked / 40% side-by-side) actually applies on first open.

- localGraph fallbackParse (go/bash): scan only the leading comment header (no body-comment phantom triggers) and strip key=value options from the asset URI; regression test added.

- docs: reject '..' in the folder arg (it writes files under f/<folder>).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(pipeline): route local previews to the // tag worker

Addresses CI review P1: the local graph/bundle dropped the parsed `// tag`, so a node annotated `// tag gpu` ran on the default worker in both `pipeline run --local` and `/pipeline_dev`, while the deployed pipeline routes it to that worker tag. Carry the tag through LocalScript / the pushed bundle / LocalScriptContent and pass it to runScriptPreview at all three launch sites. Verified: a duckdb node tagged `bash` produces a job tagged `bash`; regression test added.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(pipeline): add asset partitions/schemas routes to OpenAPI, use generated client

The ducklake asset panels (PartitionStatusGrid, SchemaHistoryPanel) hit /assets/partitions and /assets/asset_schemas via raw fetch with cookie-only auth, because those backend routes were never added to openapi.yaml so the generated client had no methods for them. On /pipeline_dev (token-via-URL, no session cookie) the raw fetches 401'd. Add both GET routes + MaterializedPartition/AssetSchemaVersion schemas to openapi.yaml and call them through AssetService, which injects the bearer token, types, and cancellation automatically. Verified: Partitions + Schema tabs load in /pipeline_dev. (backfill stays a raw fetch — it's an EE-only route not in the OSS spec — with the token added inline.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(cli): regenerate bun.lock for windmill-parser-wasm-asset

package.json / package-lock.json carry windmill-parser-wasm-asset@1.740.0 but the tracked bun.lock (the CLI installs/builds/tests via bun) was stale, so fresh bun installs would resolve a different graph than the committed lock. Regenerated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(pipeline): show asset producer + its runs in the dev-preview panel

Selecting a ducklake/asset node in /pipeline_dev showed 'No producer for this asset' because selectionProducers wasn't passed (it's derived from the deployed graph on the route page, absent here). Compute it from the local graph's w/rw write-edges (incl. the // materialize target) and pass it through, mirroring the route page — so the panel shows the producing script and its (preview) runs, including data-test failures.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(pipeline): carry annotation metadata onto local-graph runnables

The local graph emitted only path/usage_kind/in_pipeline/materialize_target per runnable, so /pipeline_dev and pipeline show --local weren't the same surface as the deployed graph for annotated scripts — missing the badges/lineage the shared canvas renders. Map the wasm-parsed partition_kind, freshness, tag, retry, data_tests, column_lineage, and materialize_strategy (derived append/merge/replace) onto each runnable, mirroring the deployed AssetGraphRunnableNode.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(pipeline): exclude event handlers that are lineage descendants from runAll

The runAll guarantee ('never fires an event handler with empty args') only held for event ROOTS — validStarts excludes them, but runAll then unions in descendants(dag, start), so a kafka/mqtt/... handler that also reads an upstream pipeline asset (a lineage descendant of a valid start) still landed in the plan. Add eventTriggerScripts() and subtract it from the selection after the descendant union. +unit test.

Also: docs/pipeline-local-dev.md recipe used 'pipeline docs demo_pipeline' without --local (default queries the deployed graph → hits the empty hint); add --local.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(pipeline): whole-pipeline run cuts at event handlers (drop their downstream too)

The prior runAll fix subtracted event handlers from the selection but left their downstream: for manual_root → asset_x → kafka_handler → asset_y → consumer, deleting only kafka_handler left consumer selected, and topoOrder then ran it as a root with missing/stale event-derived inputs. Replace the descendant-union+delete with reachableCutting(dag, validStarts, eventHandlers): traverse from valid starts but treat event handlers as cut points, so a node reachable ONLY through an event handler is dropped while one reachable via a non-event path stays. +unit test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(pipeline): recover // tag in the go/bash annotation fallback

The wasm path carries out.tag, but the go/bash fallback (and the wasm-error degradation path) only recovered pipeline + on, so a // tag gpu on a bash/go node — or a temporarily-unparseable ts/py/sql node — silently routed the local preview to the default worker while the deployed pipeline routes to the tag. Scan for // tag in fallbackParse too. +test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(pipeline): extract shared assetProducers helper

The 'who writes this asset' write-edge derivation was copied verbatim in PipelineDevView and the pipeline route page — two copies that would drift. Extract assetProducers(graph, selection) into graphTraversal.ts and use it from both, keeping the dev view and route page in lockstep.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(pipeline): only overwrite AGENTS.md/CLAUDE.md when it's the exact generated pointer

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(pipeline): wire local-dev runs into the selected-node runs pane

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(pipeline): exclude data_upload/webhook entrypoints from auto CLI runs

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(pipeline): --upload binds an object to a data_upload/webhook entry point

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(pipeline): add "Run + downstream" to the dev preview detail form

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(pipeline): cut non-autorun triggers on all run paths; multi-binding --upload

Address CI review: apply the data_upload/webhook/event barrier cut to the
single-root and bounded (--from/--to) paths, not just whole-pipeline; accumulate
repeatable --upload bindings per script (were overwritten); scope dev upload keys
by script+param to avoid basename clobbering; drop <script> from help text.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(pipeline): reseed dev run form when a local edit changes the script's args

The read-only pane is keyed on script.path only, so in /pipeline_dev the selected
node re-resolves on every WS bundle without remounting; PipelineScriptView cloned
script.schema once, so adding/removing args left the run form on a stale schema
(could run with missing inputs). Extract PipelineRunForm (owns the SchemaForm
clone) and key it on the serialized schema: a real arg change reseeds the form,
an unchanged re-resolve keeps in-progress input.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(pipeline): don't cut a scheduled/manual root that also has a non-autorun trigger

Address Codex P1: the barrier set subtracted only --upload-bound scripts, so a
script with both `// on schedule` and `// on data_upload` resolved as the start
yet was also a barrier — reachableCutting skipped it, giving an empty run plan.
Subtract all valid starts (schedule/manual roots + bound handlers) from barriers:
a legitimately-scheduled root runs on its schedule path even if it also carries a
caller-input trigger; pure input-only roots stay cut. Adds a regression test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(pipeline): deployed non-autorun enrichment, s3:// storage, --to cut accounting, tag regex

Address CI review (Codex P1/P1/P2, Pi P2):
- Deployed `pipeline run` recovers marker-only data_upload/webhook/email triggers
  from script bodies (like the `show` path) so input-only entrypoints are cut
  instead of auto-run empty on the deployed graph.
- `--upload s3://<storage>/<key>` keeps the named storage (authority) instead of
  folding it into the key, matching the S3Object round-trip convention.
- Bounded `--to` targets cut by a barrier are reported in droppedEnds (+warning),
  not reachableEnds.
- fallbackParse `// tag` matches a single token (\S+), rejecting multi-word prose.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(pipeline): header-only deployed marker scan, fail-closed enrichment, default-storage s3 keys

Address CI review (Codex P2, cubic P1/P1/P2):
- Deployed marker recovery scans the LEADING comment header only (shared
  recoverHeaderMarkers helper, reused by the show enrichment too) so a body
  comment `// on data_upload` can't inject a phantom trigger and over-cut.
- Deployed run enrichment fails CLOSED: a script-body fetch error aborts the run
  instead of silently letting an input-only entrypoint run with empty args.
- Revert `--upload s3://` to default-storage whole-path keys (matching pipeline
  `s3://` asset-URI semantics); named-storage authority-splitting broke nested
  default keys like `s3://raw/2026/events.csv`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(pipeline): reject trailing content on fallback native markers; trim s3:/// key

Address CI review (Codex P2, cubic P3):
- fallbackParse now requires a native marker (`// on data_upload`) to stand alone;
  a line with trailing content (`// on data_upload f/foo`, `# on kafka topic`) is
  rejected, matching the canonical parser and keeping local/deployed parity.
- s3UriKey trims a leading slash so the canonical empty-authority default form
  `s3:///key` doesn't leak a leading slash into the object key.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(pipeline): persist dev WS token per-port so reconnect survives a CLI restart

Address Codex P2: the /pipeline_dev auto-reconnect reuses the ws_token from the
page URL, but `pipeline dev` minted a fresh random token each start, so a restart
on the same port left the open page rejected by verifyClient forever. Persist the
token per-port under the user-private config dir (0600) and reuse it on restart,
so an already-open page reconnects — matching the reconnect behavior's intent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(pipeline): scope persisted dev WS token by workspace+folder+port

Address cubic P2: keying the persisted token by port alone let a stale browser
tab from a previous folder's session on the same port reconnect and receive a
different folder's source. Scope the token file by workspace+folder+port so a
same-session restart still reconnects, but a different folder on the same port
gets a distinct token that rejects stale cross-folder tabs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(pipeline): caller args can't override skip-dispatch guard; hash the dev token key

Address CI review (Codex P1, cubic P2):
- makeLaunch / CLI run build args with `_wmill_skip_asset_dispatch` LAST (and drop
  any caller-supplied copy) so a run-form/`--upload` arg can't re-enable backend
  asset dispatch while the client orchestrates the cascade (double-run / running
  deployed subscribers from a local preview). Adds a cascadeRun guard test.
- Dev WS token file key is a sha256 of NUL-delimited workspace+folder+port, so
  different folders (`a/b` vs `a_b`) can't collide onto the same token file.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(pipeline): canonical s3://storage/key --upload parsing; scope dev token by remote+root

Address Codex P1/P1:
- Restore canonical S3Object URI parsing for `--upload` s3 sources, matching the
  frontend's `parseS3Object` (`s3://<storage>/<key>`, empty authority ⇒ default,
  `s3:///key`/`s3:///nested/key` for the default store). `s3://secondary/k.csv` →
  `{ s3: "k.csv", storage: "secondary" }` so a named-storage object is read from
  the right store. (This is the canonical convention; the default-storage nested
  key is served by the `s3:///` form.)
- Scope the persisted dev WS token by remote+workspace+root+folder+port (was
  workspace+folder+port), so two profiles on different remotes (or local checkouts)
  with the same workspace/folder/port don't share a token — a stale tab can't
  reconnect across a workspace/remote boundary.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 11:41:36 +02:00
hugocasaandClaude Opus 4.8 edfb90d257 feat(git-sync): clearer push indicator + gate webhook delivery to app repos
- Push-on-deploy is shown with a check icon + concise line (via the
  shared GitSyncModeDisplay, restyled from the oversized "Sync:" text);
  the setup wizard reuses it without the check (pre-save preview).
- The delivery-mode selector only shows for GitHub App-backed repos;
  token-based repos show a "webhooks require the GitHub App (managed or
  GHES)" note with a docs link and poll instead. Bumps the EE ref.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 10:39:33 +02:00
Ruben FiszelandClaude Opus 4.8 5a661279a3 feat(pipelines): add managed SCD2 history materialize strategy (#9850)
* feat(pipelines): add managed SCD2 history materialize strategy

`// materialize ducklake://... key=<col> history [track=...]` (alias: `scd2`)
upgrades the keyed merge to SCD type 2: diff the current snapshot against live
rows, close changed versions (valid_to/is_current) and open new ones in one
transaction, keeping full history. Adds a consumer-convenience <dim>_current
view; effective-dated joins via native ASOF JOIN >= valid_from. Managed, so
// data_test and schema capture work (unlike manual mode). Non-partitioned v1,
soft-delete on absence.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(pipelines): document scd2 track= spacing, reserved _current suffix, schema-freeze

Addresses non-blocking CI-review nits on the new SCD2 public surface.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(pipelines): null-safe scd2 key matching + create _current view inside txn

Addresses CI review: (1) Codex P1 — NULL natural keys were flagged as changed
but silently dropped because `key IN (...)` never matches NULL; close/open now
match with `IS NOT DISTINCT FROM` via correlated EXISTS. (2) cubic P2 — the
`<dim>_current` view was created after COMMIT and CREATE VIEW advances the
DuckLake snapshot, so the summary recorded the view's snapshot instead of the
data write; the view is now created inside the write transaction. Validated both
against a real DuckLake (NULL key materialized; one snapshot per run).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(pipelines): create scd2 _current view with IF NOT EXISTS to keep no-change runs no-op

Addresses CI review (Codex P2): CREATE OR REPLACE VIEW advances the DuckLake
snapshot every run, so an unchanged rerun still minted/recorded a snapshot. The
view definition is static, so IF NOT EXISTS creates it once (folded into the
first data-write snapshot) and is a true no-op thereafter — verified an unchanged
rerun keeps max(snapshot_id) constant. Also softens the reserved-name collision:
IF NOT EXISTS skips silently instead of erroring.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(pipelines): add scd2 deletes=close (hard-delete-close)

Opt-in `deletes=close` closes the current version of a key that disappears from
the snapshot (dbt's hard_deletes=close); default stays soft-delete. Codegen adds
a vanished-key temp set (current keys EXCEPT snapshot keys) + a second null-safe
close UPDATE with no reopen; a reappearing key opens a fresh version (validity
gap = correct SCD2). Wired through both parsers with parity fixtures/tests, worker
derivation, unit + codegen tests, and docs. Verified end-to-end against a real
DuckLake incl. delete-close + reactivation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(pipelines): align materialize deploy precedence warning with runtime (scd2>append>merge)

The deploy-time conflict warning only knew append>key, so
warned 'append wins' while the runtime (duckdb_executor) runs SCD2 (history wins).
Warn for history+append (history wins, append ignored) before the append+key case,
mirroring the runtime strategy precedence. (Pi review P2.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(pipelines): register scd2 _current view as a produced asset for cascade dispatch

The docs present the companion <dim>_current view as a subscribable produced asset
(// on ducklake://.../<dim>_current), but deploy registered only the base table as
a write asset, so a subscriber on the view would never be dispatched (the cascade
fans out from deploy-time asset rows). Register <dim>_current as a produced write
asset when scd2 so those subscribers fire. (Codex review P1.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(pipelines): don't register _current asset for manual+history (no view created)

Manual mode short-circuits before the scd2 codegen, so no <dim>_current view is
created; gate the produced-asset registration on !manual so a contradictory
// materialize manual ... history doesn't leave a false write edge dispatching
subscribers on a nonexistent view. (Codex review P2.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 10:15:00 +02:00
hugocasaandClaude Opus 4.8 e0d3905e3a feat(git-sync): auto-pull UI — direction split, delivery mode, fallback notice
Reorganize the repository card into two clearly labeled directions:
"Push to Git on deploy (Windmill → Git)" and "Pull from Git (Git →
Windmill)". In the pull section:
- new connections default to auto-pull enabled (webhook with polling
  fallback); existing repos load with auto-pull off and are unchanged
- a Delivery selector chooses "Webhook with polling fallback" or
  "Polling only (air-gapped)"
- a notice surfaces webhook_error when delivery falls back to polling
- a reminder to remove any pre-existing GitHub Action that pushed into
  Windmill, to avoid conflicting double-syncs

Adds the webhook_error field to AutoPullSettings (+ openapi) and bumps
the EE ref.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 20:10:35 +02:00
hugocasaandClaude Opus 4.8 b4b0c6a93e feat: add dev workspaces paired with a lockable prod workspace (#9793)
* feat: add dev workspaces paired with a lockable prod workspace

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: gate dev-workspace prod-lock on admin and prevent attach cycles

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat: redirect locked-prod edits into the dev workspace

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: make dev-workspace settings tab available on CE (was EE-gated)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat: lock prod against forking too and funnel edits to the dev workspace

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat: open dev item page on edit and tailor dev-workspace lock messages

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat: prevent nested dev workspaces and hide dev option when one exists

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore: drop the redundant already-has-dev hint on the fork form

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat: badge dev workspaces and sort them ahead of forks in the tree/switcher

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat: label dev workspaces as 'Dev workspace of X' instead of 'Fork of X'

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat: label edit as 'Edit in <dev>', cover editor headers, auto-expand dev in tree

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat: split prod lock into separate block-deploy and prevent-forking toggles

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat: make resources/variables workspace-specific from compare page

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat: steer AI-chat sessions to the dev workspace

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat: refine session fork options and lock guidance for dev/prod

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: session picker reads prod's real rules, default to current ws

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat: copy members into forks and clarify dev-workspace root labeling

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* style: place the workspace id field under the fork name

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: address dev-workspace review findings and harden fork detection

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore: regenerate sqlx offline cache

Restores entries dropped during the origin/main merge and adds the
dev-workspace queries (is_dev_workspace, ws_specific, has_parent).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: address second-round dev-workspace review findings

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: address Pi and Codex review findings on dev-workspace endpoints

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: gate locked-dev git-branch fork on admin and validate ws_specific path

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: clear prod dev-lock when deleting an attached dev workspace

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* refactor: consolidate dev-workspace migration and scope all-group join to attach

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: restore dev-workspace CHECK into consolidated migration and scope all-group join

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* refactor: drop copy_members from the dev-workspace attach path

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: dev-workspace lifecycle/auth fixes from Codex review round

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat: explicit create-in-other for workspace-specific items

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: make create-in-other strictly create-only (never overwrite target)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: return 403 (not 401) for dev-workspace permission denials

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat: allow attaching a same-family fork as a dev workspace

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* style: emphasize the go-to-dev action in the no-direct-deploy alert

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: seed a resource's linked variables when creating it in the other workspace

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: judge workspace deploy/fork locks against the user's identity in that workspace

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* style: clarify create-in help text in workspace-specific panel

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: admin-gate dev-workspace creation and harden lock/seed edges

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: preserve a staged fork's source on picker create-mode re-entry

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: clear dev flag on archive and check dev existence server-side

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: make create-in-other atomically create-only via direct create

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: create-only resource insert, ws-specific list scopes, archive lock guard

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: reserve the dev_workspace_lock protection-rule name from the public API

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs: reattach create_protection_rule doc comment to its function

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* refactor: make dev-archive pairing teardown atomic with the archive

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: follow deploy_to on root rename; show dev pairing to non-member prod admins

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: copy creator metadata on fork; invalidate fork routing cache on rename

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: accept g/ paths in set_ws_specific; gate copy_members to dev workspaces

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 17:01:07 +00:00
Ruben FiszelandClaude Opus 4.8 83ed011e26 feat(object-store): make GCS service account key optional for Workload Identity (#9842)
build_gcs_client always called `.with_service_account_key(...)`, so an
absent key (the settings UI stores "no key" as the empty JSON object `{}`)
was handed to the builder and failed to parse instead of falling through
to the object_store crate's InstanceCredentialProvider. Skip the call when
the key is blank so GCS uses the instance's ambient credentials (GKE
Workload Identity / the GCP metadata server).

"Blank" (empty/whitespace/`{}`/`null`) is centralized in a shared
`gcs_service_account_key_is_blank` predicate so the build path and the
non-super-admin connectivity-test SSRF guard (`validate_object_storage_test`)
agree on what counts as "no key" — otherwise a blank key would bypass the
guard yet still trigger the ambient-credential fallback, letting an
untrusted caller probe arbitrary buckets with the server's instance role.

Also clarify the settings UI hint that the key may be left empty for
ambient credentials, and add regression tests for the blank-key build path
and the guard.

Fixes WIN-2110

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 15:57:26 +00:00
hugocasaandClaude Opus 4.8 a37a144e81 fix(ai-chat): replay anthropic turns verbatim to keep thinking valid (#9843)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 15:49:57 +00:00
Ruben FiszelandClaude Opus 4.8 a27e814a03 feat: add copy-to-clipboard button to rendered Mermaid diagrams in AI chat (#9838)
MermaidDisplay only showed the rendered SVG, hiding the raw source once
rendering succeeded. Add a copy button in the showSvg branch mirroring the
pattern in HighlightCode.svelte so the diagram source can be extracted.

Fixes WIN-2109

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 10:10:31 +02:00
Ruben Fiszelandrubenfiszel a9ffdb996b chore(main): release 1.743.0 (#9837)
* chore(main): release 1.743.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2026-06-29 20:41:38 +00:00
Ruben FiszelandClaude Opus 4.8 c91027824b feat(pipeline): AI-chat data-pipeline editor (route + in-session) + home surfacing (#9805)
* feat(pipeline): AI chat tools to build pipeline nodes with diff/approval

Add a data-pipeline AI chat experience modeled on the flow editor and
surfaced through the dev-gated global chat (no new chat panel).

The /pipeline editor registers PipelineAIChatHelpers on the AIChatManager;
while it is open the global mode layers pipeline tools, a pipeline prompt
section, and the helpers on top of the full global tool set (behavior is
unchanged when no pipeline editor is open).

New tools (frontend/src/lib/components/copilot/chat/pipeline/core.ts):
- get_pipeline_graph / read_pipeline_node — read the live graph and bodies
- build_pipeline_node / edit_pipeline_node — stage changes as AI-pending drafts
- remove_pipeline_node — drop a staged proposal
- test_pipeline_node — preview-run a node (requires confirmation)

Tools never deploy: they stage drafts flagged aiPending, rendered on the
canvas with an accent ring and reviewed via Accept all / Reject all (the
flow editor's GlobalReviewButtons). Accept commits the drafts; Reject reverts
to a pre-AI snapshot, preserving earlier accepted drafts. Auto-accept is gated
on the chat autonomy mode.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(pipeline): teach the global/session chat to author data pipelines

Without an open /pipeline editor the session chat had no pipeline concept, so
"create a data pipeline" loaded flow instructions and built a flow. Add a
first-class pipeline authoring path:

- system_prompts/base/pipeline-base.md — what a data pipeline is (a DAG of
  annotated scripts wired by storage assets, NOT a flow) and how to author the
  // pipeline / // on / // materialize annotations; wired through generate.py as
  getPipelinePrompt() (regenerated prompts.ts/index.ts).
- global/core.ts — new get_instructions subject "pipeline", and a global-prompt
  rule disambiguating data pipelines from flows so the model routes correctly.
- ai_evals/cases/global.yaml — two global cases (single node, two-node chain)
  asserting pipeline-annotated script drafts and forbidding write_flow, guarding
  the pipeline-vs-flow conflation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(pipeline): show & build pipelines in the AI session preview

Add a 'pipeline' session preview target so the session AI can show the
data-pipeline graph for a folder and build nodes in-pane:

- open_preview now accepts kind="pipeline" (path = folder); SessionTarget /
  EDITOR_TARGET_KINDS widen accordingly. The slot/codec load model stays
  flow|script|raw_app — pipeline bypasses it with its own fetch/draft state.
- New PipelineEditorView.svelte mounts in the session pane: fetches the
  folder graph, overlays AI drafts, renders AssetGraphCanvas + the
  Accept/Reject review buttons, and registers PipelineAIChatHelpers on the
  *session-scoped* manager (via getAiChatManager) so build_pipeline_node /
  edit_pipeline_node + the diff/approval work inside the session too.
- System prompt nudges the model to open the pipeline preview and use the
  staging tools while building.

Verified end-to-end with a real model: the session AI called open_preview,
the graph mounted in the side panel, then build_pipeline_node staged a node
on the session canvas with its schedule trigger and ducklake output.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(pipeline): share the AI editor logic between route page and session

Consolidate the duplicated data-pipeline AI logic onto a single shared layer so
the route editor and the in-session preview behave identically and the session
gains the full code editor.

- New pipelineAiHelpers.ts: createPipelineAiHelpers(deps) owns the propose/edit/
  remove/accept/reject/test staging + the per-turn snapshot bookkeeping that
  powers Reject. Callers inject accessors for their own draft Map and graph.
- Route page (/pipeline/[folder]) drops its ~250-line inline AI-helper block and
  wires the shared factory via deps (folder/workspace/graph/drafts + focus,
  ensureEditable, run-started). Its shell — persistence, navigation guard,
  activity, cascade, trigger drawers — is untouched.
- Session PipelineEditorView uses the same factory and now renders the real
  AssetGraphDetailsPane (code editor + live overlays + test), so a node built in
  a session opens with its source, matching the route editor.

Verified: route page hydrates/renders drafts unchanged; in a session the AI
opened the pipeline preview, built a node, and its code showed in the details
pane. check:fast clean, 197 unit tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(pipeline): externalize editor state into PipelineEditorState (step 1)

Introduce PipelineEditorState — the data-pipeline analogue of the flow editor's
flowStore. It owns the draft Map, the live editor overlays, and the selection,
with callback-safe methods (handleDraftPersist / handleAnnotationsChange / … ),
so a single editor can be rendered by both the route page and the session.

This commit lands the store and points the in-session PipelineEditorView at it
(no behaviour change — the session already had these inline). Next steps move the
route page onto the store and a shared <PipelineGraphEditor>.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(pipeline): point the route editor at PipelineEditorState (step 1)

Move the route page's draft Map, live editor overlays, selection, and the
draft-persist / live-change handlers onto the shared PipelineEditorState (`pe`),
referencing them as `pe.*` in place. No behaviour change — persistence, graph
resolution, run dispatch, AI staging, and deploy all stay on the page and now
read/write the externalized state.

This is the data-pipeline analogue of the flow editor's flowStore: the route
page and the in-session preview now share one source of editor truth, setting up
the shared <PipelineGraphEditor> in the next steps.

Verified: the page hydrates its DB draft, renders the overlay graph, the toolbar
counts (Save all (N)) track pe.drafts, and selecting a node opens it in the
details pane. check:fast clean, 84 unit tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(pipeline): render the route editor via shared PipelineGraphEditor (step 2)

Extract the canvas + details-pane editor body into PipelineGraphEditor.svelte,
the data-pipeline analogue of FlowBuilder. The route page now delegates its
Splitpanes block to it, passing the externalized PipelineEditorState plus its
run/cascade/trigger/deploy callbacks; the component owns pane sizing,
selection/details-open derivation, and the canvas+details rendering.

Root-caused the earlier ts2769 "$props() No overload" to a prop named `state`
colliding with the `$state` rune (`let x = $state(...)` parsed as a store
auto-subscription on the prop) — the prop is now `editor`.

Net: the route page sheds ~310 lines of template/state; behaviour preserved.
Verified: the page hydrates its DB draft, renders the graph, opens the draft in
the details pane (live code editor + Test), pane sizing works. check:fast clean,
24 pipeline tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(pipeline): move draft autosave into PipelineGraphEditor (step 3)

Fold the per-user `data_pipeline` DraftService bundle autosave (hydrate +
debounced persist + localStorage crash mirror) into PipelineGraphEditor, gated
by a `persistDrafts` prop — FlowBuilder's parameterized-autosave shape. The route
page passes `persistDrafts` + `folder` and reads `editor.loadedFromDbDraft` for
its AutosaveIndicator; the in-session preview will leave persistence off.

Also restores the `untrack(...)` wrapping on the pane-sizing $effect (dropped
when the editor body was extracted in step 2). Without it the Pane `bind:size`
feedback loops the effect and pegs the main thread when the details pane is
closed — a latent hang in the step-2 commit.

check:fast clean, 24 pipeline tests pass. Note: browser revalidation was not
possible this session (the Playwright MCP browser was reset); the autosave is a
verbatim port and the untrack fix is the original working form.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(pipeline): render the session preview via shared PipelineGraphEditor (step 4)

Point the in-session PipelineEditorView at the shared PipelineGraphEditor instead
of its own inline canvas + details pane. The session now renders the exact same
editor body as the route page — gaining the full details/code pane — while opting
out of persistence (persistDrafts=false) and the run/cascade/trigger/bounded
affordances (their callbacks are omitted, so those controls hide). Building nodes
+ the Accept/Reject diff still work via the AI helpers.

Also fixes issues surfaced by a full `svelte-check` while wiring this up:
- PipelineGraphEditor: edit mode opened the details pane unconditionally (a step-2
  regression); restored the route's "open only on selection/draft" behaviour.
- Route page passed an `isOperator` prop the component doesn't accept (step-2;
  caught only by full check, not check:fast).
- SessionItemNotFound: narrow its `kind` to exclude `pipeline` (pipeline targets
  never slot-load, so they can't 404 through it) — closes the SessionTarget-widen
  fallout.
- PipelineEditorView: cast the resolveGraph base to AssetGraphResponse.

Full `svelte-check` now clean across all pipeline/session files; 137 unit tests
pass. (Browser revalidation still pending — Playwright MCP was unavailable this
session; see the smoke-test note on the PR.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(pipeline): stop an infinite microtask loop when persisting a no-output draft

handleDraftPersist short-circuits when the open draft's content + inferred writes
are unchanged. The writes check compared `d.outputAssets?.length === writes.length`,
but a no-output draft has `outputAssets: undefined` (so `?.length` is `undefined`)
while the details pane infers an empty `writes: []` (length 0). `undefined === 0`
is false, so it never short-circuited: every persist re-wrote the drafts Map with
an equivalent object, which gave `activeDraft.script` a new identity → the pane
re-emitted its overlays → the graph re-derived → persist fired again. A self-
sustaining microtask loop that pegged the renderer and froze the tab on any
pipeline carrying a no-output draft (e.g. hydrating one from the saved
data_pipeline draft on load). It hangs rather than throwing effect_update_depth_
exceeded because it cycles across microtasks, not within one reactive flush.

Fix: coalesce the undefined length to 0 so "no outputs" compares equal to an empty
inferred-writes list. Adds pipelineEditorState.test.ts covering the idempotency
(fails without the fix) plus the change/no-change cases.

Root-caused by instrumenting the reactive churn: every iteration reassigned
drafts/liveContent/liveBodyAssets/liveAnnotations/displayGraph with identical
values — pure reference churn off the drafts re-write.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(ai-chat): make the agent open the pipeline editor before building nodes

In a session, the GLOBAL system prompt only *advised* opening the pipeline preview
("show its graph with open_preview ... prefer those tools once it is open"), so
the agent routinely skipped it: on a plain "build a data pipeline" request it
reached for write_script and staged plain script drafts, and the canvas editor
never opened. build_pipeline_node / edit_pipeline_node are only registered once
the preview is open, so skipping open_preview also loses the canvas-staged
Accept/Reject diff-approval flow entirely.

Make the guidance imperative: open_preview(kind="pipeline", path=<folder>) is the
FIRST step before creating any node (an empty or not-yet-created folder is fine —
create_folder first if needed), and pipeline nodes go through build_pipeline_node
/ edit_pipeline_node, never write_script. This also clears the agent's "the folder
might not exist" hesitation that pushed it toward write_script.

Verified live (same plain prompt, before/after): before it used write_script with
no editor; after, the agent opens the editor first and stages a canvas-highlighted
node with Accept all / Reject all. The guidance is gated on previewTools
(session-only), so it doesn't affect the non-preview global eval cases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(pipeline): preserve in-session pipeline drafts across editor hide/show

The session preview's PipelineEditorState lived in the PipelineEditorView
component with persistDrafts=false. Hiding the editor sets editorVisible=false,
which makes `hasEditor` false and the `{#if hasEditor}` block unmount the view —
discarding its component-local store. Showing it again remounted a fresh, empty
one, so the pipeline the AI had built in the session vanished.

Move the PipelineEditorState onto the per-session SessionRuntime (like the flow /
script / raw_app editors, which already host their state there and take {runtime}),
so it survives the pane unmount on hide and across session switches. The runtime
is keyed by session id and only dropped on session deletion.

Because the instance is now reused, guard against a retarget to a different
folder: PipelineEditorView resets the state when `path` changes to a new folder
(a same-folder remount keeps the drafts). Adds `folder` + `reset()` to the store.

Verified: build a node in a session → Close editor → Show editor → the staged
node, its wiring, the details-pane code, and Accept/Reject all re-appear. Full
svelte-check clean; 139 pipeline tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(pipeline-ai): clearer diff + persistent review banner on the canvas

The AI review affordance had two problems on the pipeline canvas:

- The floating Accept-all / Reject-all bar sat bottom-center, where it
  collided with the minimap once the canvas narrowed on node selection —
  reading as "the buttons vanished when I select a node".
- Every staged draft rendered with the same blue ring, so it wasn't clear
  what the review would actually change (a plain manual draft looked the
  same as an AI proposal).

Replace the floating bar with a top-left review banner (z-30, clear of the
controls and minimap) that stays put regardless of selection and spells out
the pending counts. Color the diff per node: a proposal that adds a node
that isn't deployed rings green with a "new" chip; one that edits an
already-deployed node rings amber with an "edited" chip. Plain manual
drafts keep the neutral gray dashed border, so only the green/amber nodes
read as part of the Accept/Reject set.

aiPendingKind is resolved in resolveGraph (deployed runnable present →
modified, else added) and forwarded through the canvas to the node.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(pipeline-ai): persist in-session pipeline proposals across reload/switch

Staged AI proposals lived only in the per-session runtime's in-memory
PipelineEditorState (persistDrafts=false), so a page reload — and an
LRU-evicted runtime on session switch — dropped them, leaving the canvas
and the Accept/Reject review empty even though the chat still showed the
nodes as staged.

Enable the same per-folder DB-draft persistence the route page uses for the
in-session editor. To keep hide/show cheap and race-free, hydration is now
gated per editor instance (PipelineEditorState.hydratedFromDb) rather than
per component mount: the runtime-hosted instance hydrates ONCE when fresh
(reload / evicted runtime) and then keeps its in-memory drafts across the
editor pane unmounting on hide — re-reading the DB on every remount would
race a not-yet-flushed autosave and drop a just-staged draft. A folder
retarget resets the flag so the new folder re-hydrates.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(pipeline-ai): make Reject all work for rehydrated proposals

rejectAll only reverted paths tracked in the in-memory aiSnapshots map,
which is rebuilt empty on each editor mount. After a reload (or session
switch into a fresh runtime) the proposals are restored from the persisted
draft but have no snapshot, so Reject all was a no-op on exactly the nodes
it should discard. Sweep any still-pending draft without a snapshot and
discard it (revertPath with no snapshot deletes the path; for an edit of a
deployed node that correctly falls back to the deployed body). Adds unit
coverage for accept/reject including the no-snapshot case.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(pipeline-ai): keep proposals visible while the graph reloads on switch

The session editor pane is LRU-capped (MAX_WARM_EDITORS), so returning to a
session whose pane was evicted remounts PipelineEditorView with a fresh
graphRes resource (loading=true, current=undefined). The deployed-graph
loading spinner gated the whole canvas, so the staged proposals and the
Accept/Reject review banner vanished until the re-fetch resolved — read as
"the proposal disappears when I switch sessions".

Only show the loading/error placeholder when there are no drafts to display.
When the runtime already holds staged drafts, render the editor immediately:
resolveGraph overlays them on an empty base so the proposals + banner stay
visible, and the deployed nodes fill in when the fetch completes. Verified
with a 4s-delayed graph fetch — proposals render through the load with no
spinner.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* refactor(pipeline-ai): apply AI node edits directly as drafts, no approve/reject

The canvas-level Accept all / Reject all review (aiPending proposals, the
green/amber diff ring + "new"/"edited" chips, and the review banner) didn't
fit the pipeline editor. Match the flow/script editor instead: build/edit
apply directly as ordinary unsaved drafts on the canvas, which the user then
deploys — there is no separate approval step.

Removed across the surface:
- aiPending / aiPendingKind on the runnable node + resolveGraph seeding +
  canvas forwarding; AI-built nodes now render with the existing plain
  unsaved-draft dashed styling.
- the review banner, count derivations, and hasAiPending/onAccept/onReject
  props from PipelineGraphEditor and both consumers (route page + session
  view).
- acceptAll/rejectAll/hasPending and the per-turn snapshot bookkeeping from
  the shared helpers; removeProposedNode now just discards the unsaved draft
  at a path (undo a build). acceptAllProposals/rejectAllProposals/
  hasPendingProposals dropped from the PipelineAIChatHelpers interface and
  the manager's auto-accept hook.
- accept/reject language from the tool descriptions, return messages, and the
  system-prompt section.

Tests updated; pipeline + AssetGraph suites pass (142).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(drafts-diff): support data_pipeline diffs + fix blank empty-summary row

Two issues in the session "Drafts" diff drawer (DraftDiffDrawer):

- Clicking a `data_pipeline` bundle row threw "Draft diff not supported for
  kind data_pipeline" (utils_draft_deploy.ts) — there was no handler for the
  kind, so it fell to the OVERLAY_GETTERS lookup and errored. The bundle has
  no deployed counterpart (each node deploys individually as a script), so
  diff it node-by-node: surface each node's draft body keyed by path, folding
  in the deployed body as the "before" when a node edits a deployed script.

- A draft row whose summary is an empty string (e.g. the app draft) rendered
  with no title at all: WorkspaceItemRow's single-line branch used
  `summary ?? secondary`, and `??` doesn't treat '' as absent, so it showed
  the empty summary instead of the path. Use `||` so an empty summary falls
  back to the path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(drafts-diff): explode data_pipeline bundle into per-node subitems

A data_pipeline draft is a bundle of node-script drafts, so a single row
diffed the whole thing as one blob. Explode it in DraftDiffDrawer into one
script row per node, nested under the bundle's `…/data_pipeline` folder so
they read as the pipeline's subitems — each with its own path and a proper
script Content/Metadata code diff. The node's draft body is the "after"; its
deployed body (when the node is already deployed) is the "before", so edits
show as line diffs and new nodes as added. A single bundle row (via the
getDraftDiffValues data_pipeline fallback) is kept only for the case where
the bundle can't be read.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* refactor(pipeline-ai): simplify — drop vestigial approve/reject scaffolding & redundant field

Review pass over the PR, removing complexity left from the approve/reject
removal and the shared-component refactor (all behavior-preserving):

- Inline the `acceptPendingEdits` pass-through into `acceptPendingFlowEdits`
  and revert the now-inert `autoAcceptEditsAvailable` GLOBAL+pipeline widening
  (pipeline edits are direct drafts — nothing to auto-accept).
- Fix the global system prompt: pipeline tools "apply directly as unsaved
  drafts (no accept/reject)", not "proposals the user Accepts or Rejects".
- Collapse the redundant `outputAsset` (singular) into `outputAssets`,
  removing a whole resolveGraph fallback tier; simplify propose/editNode.
- Drop the single-field `PipelineAiHelpersHandle` wrapper (callers just
  destructured `{ helpers }`); inline the misleading `isoNow()` helper.
- Remove the now-unreachable `data_pipeline` branch in getDraftDiffValues
  (the drafts drawer explodes bundles per-node; an unreadable bundle is
  skipped) and the "Step N consolidation" drafting narration.
- Un-export internal-only types; reuse `storageKey`; refresh stale comments
  that still referenced proposals / the review banner / diff-approval.

svelte-check clean; 141 unit tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(pipeline): tooltip clarifying the Create/Save button deploys

The accent button in the asset-graph details pane ("Create" for a new script,
"Save" for an existing one) is really a deploy, but had no tooltip explaining
that. Add a title — "Deploy this new script to the workspace" / "Deploy your
changes to this script" — keeping the create-vs-update label distinction while
making clear both deploy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(pipeline-ai): document the `materialize` annotation in the pipeline prompt

The model invented "materialize run" because the prompt only mentioned
`// materialize <uri>` in passing. Spell out what it is in both the in-app
pipeline prompt (getPipelinePromptSection) and the base prompt
(pipeline-base.md, regenerated): a MANAGED output where the runtime writes the
table around a single SELECT (no manual CREATE/INSERT); replace (default) vs
`append` vs `key=<col>` strategies; `manual` to opt out (track-only); and its
pairing with `// partitioned …` (runs once per partition, `{partition}` token
substituted at run time). Explicitly: materialize is an output declaration,
not a command — there is no "materialize run".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(pipeline-ai): trigger drawers in the AI session preview

Bring the route page's native-trigger affordances to the in-session pipeline
editor by reusing the shared <PipelineTriggerEditors> (no duplication of the
drawer UI). Clicking a "Schedule · Missing — no trigger row" node (or
edit/delete on an attached trigger, webhook, data-upload) now opens the same
drawers the full editor uses, instead of doing nothing. Draft nodes get the
same "save the script first" guard (a trigger row needs a deployed script).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(pipeline-ai): run buttons + live run state in the AI session preview

Wire the per-node Run button and live run-state badges into the in-session
pipeline editor, reusing the shared folder-scoped job poll
(useActiveRunnableIds) the route page uses — node badges, the event log, and
the zero-latency "running" hint all come from it. The session runs one node at
a time (preview for an unsaved draft, the deployed version otherwise),
skipping the route page's cascade/deploy-queue machinery the AI-session UX
doesn't need. Verified: a node's Run button dispatches a job and the badge
updates live from the poll.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(pipeline): label the node deploy button "Deploy" (was Create/Save)

Users read "Create" and asked whether it deploys. It does — and the main
script editor's DeployButton already says "Deploy", so this is the consistent
term. Use "Deploy" for both the new-script and existing-script cases; the
new-vs-changes nuance stays in the button's tooltip.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(home): surface data pipelines as units, including bundle-phase drafts

Treat a data pipeline as one home entry instead of scattering its member
scripts:

- The home "Pipeline · f/<folder>" entry now also covers bundle-phase
  pipelines — a folder that so far only exists as a `data_pipeline` draft —
  not just deployed ones, so a pipeline shows up the moment its first node is
  drafted (union listPipelineFolders + data_pipeline draft folders).
- Pipeline-member scripts (`auto_kind='pipeline'`) are filtered out of the
  individual scripts list; they're represented by their pipeline's entry.
- Tree view injects pipeline folders so they (and their "Pipeline" entry)
  still appear when their only scripts are hidden members or they have none
  deployed yet.

Verified in both list and tree view: app_groups (deployed member folded) and
a draft-only nyc_transit both show as pipelines; the member script no longer
lists individually.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(scripts): compute auto_kind for draft-only pipeline nodes

A never-deployed pipeline node (a script draft starting with `// pipeline`)
had no script row, so list_scripts synthesized it with `auto_kind: None` — and
the home page therefore couldn't tell it was a pipeline member, listing it
individually instead of folding it into its pipeline. Parse the draft content
the same way the create path does (`parse_pipeline_annotations(...).in_pipeline`)
and set `auto_kind = "pipeline"` on the synthesized draft-only row, so draft
nodes fold into their pipeline like deployed members.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(search): hide pipeline-member scripts from global search

The Ctrl+k global search listed pipeline-member scripts (`auto_kind='pipeline'`)
individually. Filter them out — they're reached through their pipeline, matching
the home page. Deployed members carry auto_kind from the script row; draft-only
members now do too (computed from draft content in list_scripts), so both are
excluded here.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(pipeline): address PR review findings

Session run dispatch (the one real bug):
- runNode now passes `_wmill_skip_asset_dispatch: true` for a single-node run
  of a deployed node unless the user chose "run + downstream" (cascade) —
  previously a single Run could fan out to downstream deployed scripts via the
  backend asset dispatcher and fire side-effecting production runs.
- onRunProducer guards `kind === 'script'`; onTestStateChange only clears the
  run hint for the script the pane finished (not a different in-flight node);
  clear the hint on folder retarget; gate the background poll on isActiveSession
  so hidden warm panes don't poll; note the PipelineTriggerEditors workspace
  coupling.

Home page pipeline surfacing:
- Fold pipeline-member folders into `pipelineFolders` (captured in loadScripts)
  so a members-only / draft-only-`// pipeline` folder still shows its pipeline
  entry instead of vanishing; and don't render the empty-state when only
  pipelines remain (they aren't part of the text filter).
- Insert injected tree folders in name order instead of prepending.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(pipeline-ai): make clear `// materialize` is DuckDB + DuckLake only

The model put `// materialize` on a python3 node, which deploy rejects ("only
supported for DuckDB scripts"). The prompt only implied SQL ("write the body
as a single SELECT") without stating the hard constraint. Spell it out in both
the in-app prompt and pipeline-base.md: `// materialize` is DuckDB-only and its
target must be a DuckLake table; for python3/bun/postgresql nodes, write the
output via the SDK instead and let it be inferred — reach for duckdb when a
node should materialize a DuckLake table.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(pipeline-ai): fix stale comment — session now wires run + trigger affordances

Addresses review: the comment still claimed the session 'opts out of the
run/cascade/trigger/bounded affordances', but run buttons + trigger drawers
were wired in. Describe the current state (wires run + triggers; omits only
cascade/bounded/add-script).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(pipeline): address Codex review — test_pipeline_node dispatch + tree search

- [P1] testNode (the test_pipeline_node tool) ran a deployed node via
  runScriptByPath without `_wmill_skip_asset_dispatch`, so previewing one node
  could fan out to downstream deployed subscribers and run side-effecting
  scripts. Add the skip flag (test is always single-node) + a regression test.
- [P2] Home tree view injected pipeline folders — and rendered their Pipeline
  row — even during a text search, surfacing unrelated pipelines. Gate both the
  TreeViewRoot injection and TreeView's hasPipeline on `!isSearching`, matching
  the list view which hides pipeline rows on a query.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(pipeline-ai): keep the pipeline prompt after update_user_instructions

rebuildGlobalSystemMessage (called by the update_user_instructions tool)
rebuilt only the base Global prompt, dropping the pipeline-editor section that
configureGlobalMode appends. So after the chat remembered an instruction, the
next GLOBAL turn lost the active /pipeline/<folder> context + direct-draft/
materialize guidance while pipeline tools stayed registered. Re-append the
pipeline section here when a pipeline editor is registered.

Addresses Codex review [P2].

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(home): gate pipeline entries by kind/archived/owner filters

Codex review [P2]: pipeline rows/folders rendered independently of the item
filters, so a pipeline still showed under the Flows/Apps tabs, in the archived
view, and outside a selected owner. Add `visiblePipelineFolders` applying the
same gates the items get (kind ∈ {all, script}, not archived, owner-prefix
match) and route the list rows, tree injection, and empty-state check through
it. Pipelines are always `f/<folder>`, so the user-folder toggle and kind=script
keep including them.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(pipeline): address review — route folder-switch state, AI node guards, diff identity

claude[bot] [P1]: the route page's in-app folder switcher navigates same-route
(no remount), but nothing reset PipelineEditorState — so folder A's drafts
displayed under B and autosave persisted them into B's bundle, and B never
hydrated. Reset pe on folder change (mirror the session retarget), and guard the
shared hydrateDrafts against a stale folder result landing after a retarget.

codex/claude [P2]: build_pipeline_node (proposeNode) only checked drafts.has —
now rejects a path outside the open folder and one colliding with an existing
deployed node (model should edit_pipeline_node). + 3 regression tests.

codex/claude [P2]: exploded pipeline-node diff rows shared `script/<path>` with a
standalone script draft at the same path, colliding in the {#each} key + value
cache. Add an explicit unique `key` (the distinct bundle-nested path) on DiffRow;
pipeline nodes set/look up by it while `path` stays the real edit target.

claude [P2]: session AI test_pipeline_node now arms the live run badge
(onRunStarted), matching the route page.

nit: pipelineAiHelpers.test uses afterEach(restoreAllMocks) instead of an
unreachable inline mockRestore.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(pipeline): harden AI node mutations + close home label-filter / rename gaps

Codex [P1] (AI mutations trust model paths) — fully scoped now:
- editNode validates the open folder too (proposeNode already did), via a shared
  assertInFolder; an edit_pipeline_node for f/other/* no longer persists an
  unrelated script into the current folder's data_pipeline bundle.
- both build_pipeline_node and edit_pipeline_node now require the `// pipeline`
  annotation (assertPipelineAnnotation) so a staged draft is definitionally a
  pipeline member, not a silently-non-member script. + tests.
  (proposeNode's folder + deployed-collision guards landed in the prior commit.)

Codex [P2] home label filter — visiblePipelineFolders ignored labelFilter, so a
label selection still showed every pipeline (and the empty-state fell through to
render pipeline rows). Pipelines carry no labels, so a label filter hides them.

Codex [P2] session rename — PipelineEditorView now wires onScriptRenamed
(repoint selection + refetch), matching the route page; a persisted-script
rename no longer leaves the canvas on the old path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(pipeline-ai): language-specific comment prefix for annotations

Codex [P2]: the tool schema and prompt told the model to write `// pipeline` /
`// on` / `// materialize` regardless of language, and pipeline-base.md grouped
SQL with `#`. A `//` (or `#`) annotation line is invalid in a DuckDB/Postgres
node — it passes the frontend parser (which strips `//`/`--`/`#`) but is a SQL
syntax error at deploy/run. Make the guidance language-specific everywhere:
`--` for SQL (duckdb/postgresql), `#` for python3/bash, `//` for bun/TS — the
`//` in examples is the TS form to translate. Regenerated the prompt outputs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(pipeline-ai): re-scope Global prompt on folder switch + language-aware base prompt

Codex [P2] x2:
- The route page resets editor state on an in-app folder switch, but the Global
  chat's system message kept the old `/pipeline/<folder>` scope (the helper
  methods read the reactive folder, but the prompt string is only rebuilt on
  Global-mode reconfigure). Rebuild it on folder change so the next turn targets
  the new folder.
- The pre-editor base Global prompt (seen before open_preview/get_instructions)
  still showed TS-only `// pipeline` / `// on`. Make it language-aware (`--` SQL,
  `#` Python/Bash, `//` TS) so the model can't draft invalid DuckDB/Postgres
  nodes before the pipeline tools are registered.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(pipeline-ai): authoritative new-node probe + SQL-correct eval checklist

Codex [P2] x2:
- build_pipeline_node's collision check relied on the resolved graph, which can
  be empty while the session preview races open_preview (a build could shadow a
  deployed node before the graph loads) and only covered pipeline runnables, not
  a non-pipeline script at the same path. Add an authoritative backend probe
  (ScriptService.getScriptByPath): any deployed script at the path → reject with
  "use edit_pipeline_node". + regression test (empty graph, deployed script).
- The DuckLake eval judgeChecklist required the exact `// pipeline` annotation,
  which would penalize the now-correct `-- pipeline` SQL output (or reward
  invalid DuckDB syntax). Make both cases syntax-aware.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(pipeline-ai): rebuild Global prompt on session preview folder retarget

Codex [P2]: open_preview(kind="pipeline", path="B") can retarget an existing
pipeline preview from folder A to B without remounting. The retarget effect
resets editor state and the helper methods read the new path, but the
registration effect only depends on isActiveSession, so the Global system
message stayed scoped to /pipeline/A. Mirror the route-page fix: rebuild the
global system message on retarget (gated on isActiveSession — only the active
session's helpers are registered; a hidden session reconfigures when it next
becomes active).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(pipeline-ai): edit_pipeline_node preserves deployed script metadata

Codex [P1]: editNode kept only the deployed script's language and staged a fresh
makePipelineScript draft with empty hash/summary/description/tag/schema/settings.
Deploying that edit from the pane (auto_parent) would update the script while
wiping its metadata, and the route "Save all" path (no parent_hash) could hit
the backend path-conflict branch on the occupied path. Base the draft on the
existing draft's / deployed script object and replace ONLY content (+ inferred
output assets), preserving hash and metadata. + regression test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 20:33:50 +00:00
hugocasaandClaude Opus 4.8 2e9a3c57bd fix(git-sync): refresh auto-pull tooltip; bump EE ref for webhook secret encryption
The auto-pull toggle tooltip claimed GitHub App repos would sync via
webhooks "in a future update"; webhook delivery now works, so describe
the webhook-vs-polling behavior accurately. Bump the EE ref to pick up
encrypting the webhook HMAC secret at rest.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 20:26:42 +02:00
hugocasa 8e5f43f87d Merge remote-tracking branch 'origin/main' into explore-git-sync-improvements
# Conflicts:
#	backend/ee-repo-ref.txt
#	backend/windmill-worker/src/result_processor.rs
2026-06-29 19:24:54 +02:00
2493eaf031 feat(home): redesign create-new popover and home header (#9827)
* feat(home): redesign create-new popover and home header

Replace the home page "Home" title with a hover-driven "New" popover
(CreateActionsMenu) listing Script / Flow / Workflow-as-Code / Apps with
a description pane. Workflow-as-Code offers a Python / TypeScript choice;
other entries are created by clicking the list row. Move CLI/MCP to the
header far right, add a Hub link button, and drop the Workspace/Hub tab
switcher so the home page shows only the workspace list.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(home): keep Home title, move New to the right, swap popover panes

Reintroduce the "Home" header title on the left and place the New popover
on the right alongside the Hub and CLI/MCP buttons (top-aligned, with extra
gap before New). Swap the popover panes so the description is on the left
and the option list on the right; the menu opens leftward again.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(home): make Hub, CLI/MCP and New header buttons uniform md size

Set all three header buttons to unifiedSize="md" (New keeps the accent
variant to stand out) and re-center the right group now that heights match.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(home): remove divider between popover panes

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(home): badge Workflow-as-Code as Advanced and low-code App as Legacy

Add inline pills (Advanced / Legacy) next to the option label and in the
description header, and widen the option list so the longest label plus
badge fits without truncating.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(home): reorder create options and share App icon

Order is now Script, Flow, App (full-code), Workflow-as-Code, App (low-code).
Full-code App reuses the low-code App dashboard icon (distinguished by accent).
Broaden Option.icon to also accept the BarsStaggered (Flow) component.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(home): add import / pipeline secondary actions to popover detail panels

Surface the previous create-menu extras in the matching detail panel:
Flow → Import flow + Pipeline (alpha); Workflow-as-Code → Import
Workflow-as-Code; App (full/low-code) → Import full/low-code app. A shared
YAML/JSON import drawer parses the pasted source into the relevant store
(or sessionStorage for the full-reload apps_raw route) and navigates.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(home): add Data pipelines editor option (alpha) to create popover

Add a first-class "Data pipelines editor" entry right after Workflow-as-Code
(indigo accent, Workflow icon, emerald Alpha badge) routing to /pipeline, and
drop the now-redundant "Pipeline (alpha)" secondary action from the Flow panel.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(home): emphasize import buttons and group the badged options

Render the detail-panel import actions as default (bordered) buttons with an
import icon instead of subtle text, and add a separator in the option list
between the three plain options and the three badged (Advanced/Alpha/Legacy)
ones.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(home): increase the y gap around the option-group separator

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(home): rename Data pipelines editor option to Data pipelines

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(home): click-open create popover with import submenu and toggleable docs

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(home): keyboard-navigable create popover via melt dropdown with looping

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Guilhem Lemouel <guilhemlemouel@gmail.com>
2026-06-29 16:19:31 +00:00
hugocasaandClaude Opus 4.8 0827a1a36f chore(git-sync): bump init-repository hub script to v28784
Picks up the clone_ref param (windmill-integrations#158) so the phase 4 PR-check
dry-run can clone the PR head. Backward compatible; manual pull/push and the
automated pull/poller/webhook all move to the same published version.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 15:15:58 +02:00
Ruben Fiszelandrubenfiszel 96c0ff65bd chore(main): release 1.742.0 (#9830)
* chore(main): release 1.742.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2026-06-28 14:33:27 +02:00
Ruben FiszelandClaude Opus 4.8 da45e699c8 feat(apps): add labels input to app editor deploy drawer (#9828)
* feat(apps): add labels input to app editor deploy drawer

The labels feature (c4c9ef5fd) wired LabelsInput into the script, flow,
schedule, resource and variable editors but left the app editor out: it
had no labels state and createApp/updateApp never sent labels, so apps
could not be labeled from the UI despite full backend support.

Thread the deployed app's labels from the edit page through AppEditor
into AppEditorHeader, render LabelsInput in AppEditorHeaderDeploy after
the summary field (matching ScriptBuilder/FlowSettings), and include
labels in the create/update request bodies, the savedApp snapshot, and
the diff/deploy comparison values. The raw-app editor shares the deploy
drawer, so it is wired symmetrically (createAppRaw/updateAppRaw + the
raw page loader) to avoid leaking a non-functional input there.

Fixes WIN-2107

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(apps): drop redundant labels cast in app edit restore path

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(apps): include labels in deploy-drawer Diff current value

The Diff button inside the deploy drawer built its current value without
labels, so the approval preview could hide label changes that would be
deployed. (Identified by cubic.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(apps): reset raw-app labels on new-draft seed

The raw-app edit route keeps labels as route-level state and the
?new_draft=true seed-template branch never cleared it. Since the route is
reused across raw-app navigations, opening a labeled raw app then creating
a fresh one could remount RawAppEditor with the previous app's labels and
deploy them via createAppRaw. Reset labels with the other bleed-prevention
resets at the top of the new-draft branch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 11:43:09 +02:00
Ruben FiszelandClaude Opus 4.8 c479afab8e fix: redeploy older app version from deployment history (#9826)
* fix: redeploy older app version from deployment history

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: apply restored app version to low-code editor on redeploy

Redeploying an older app version from Deployment History fired the
restore callback (toast shown) but the canvas kept displaying the
current version, and Deploy then shipped that current value.

AppEditor seeds its working state from `appDraftHandle.draft ?? app`,
preferring the per-path autosave over the freshly restored `app` prop.
The remount triggered by the restore therefore re-read the stale
pre-restore draft. `reloadDeployed` already clears the draft before
remounting for the reset-to-deployed flow; `onRestore` was missing the
same step.

Drop the autosave in `onRestore` so the remounted editor seeds from the
restored value. Raw apps are unaffected: RawAppEditor binds `files`
directly (no draft precedence), and `extractRawApp` mutates that bound
state in place.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(raw-apps): convert savedNewAppPath event forwarding to a callback prop

`svelte-check` (CI `npm check`) failed with one error: forwarding the
`savedNewAppPath` createEventDispatcher event through the runes-mode
RawAppEditor → RawAppEditorHeader chain types as "not assignable to
never". This is the same legacy-forwarding-through-runes pattern already
removed for `restore` in this PR — `on:savedNewAppPath` would likewise be
dropped at runtime, breaking navigation to the new path after a deploy
that renames the app.

Replace the `on:savedNewAppPath` forwarding with an `onSavedNewAppPath`
callback prop threaded page → RawAppEditor → RawAppEditorHeader, matching
`onRestore`. The header now invokes the callback instead of dispatching,
and its now-unused createEventDispatcher is removed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 11:25:59 +02:00
Ruben FiszelandClaude Opus 4.8 003a262a4e feat: column-level lineage for DuckLake pipelines (SQL-AST inferred + traceable) (#9814)
* feat: column-level lineage for ducklake pipelines via // column annotation

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat: auto-derive column lineage from DuckDB SQL AST (annotation as override)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs: clarify column-lineage inference is server-side; drafts use annotations

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(frontend): surface inferred column lineage in live pipeline drafts

Threads the DuckDB SQL-AST column lineage (from the WASM asset parser) through
ScriptEditor -> details pane -> page -> resolveGraph, merged with // column
annotations (annotation wins) so the live preview matches the deployed graph.
Takes effect once windmill-parser-wasm-asset is republished with the inference.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(frontend): bump windmill-parser-wasm-asset to 1.740.0 for SQL column-lineage inference

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs: column-lineage inference now runs live (WASM) too, merged with annotations

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(frontend): transitive column-lineage trace (impact analysis)

Stitches every producer's column_lineage into a pipeline-wide column graph
(columnLineageGraph.ts) and replaces the single-hop diagram with an
interactive ColumnLineageTrace: select an asset to see its columns' full
upstream/downstream lineage across scripts; click any column to highlight its
complete transitive impact set (forward + backward) and dim the rest.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: address CI review on column lineage (parse-fallback, node-id, perf, leak)

- backend: DuckDB SQL parse failure now falls back to `// column` annotation
  lineage instead of dropping it (Codex P1)
- columnLineageGraph: collision-proof JSON node ids; deterministic first-write
  output anchoring when a producer has multiple ducklake writes (cubic P2 ×2)
- pipeline page: gate buildColumnGraph to a ducklake-asset selection so it
  doesn't rebuild on every editor keystroke (cubic P2)
- ScriptEditor: clear inferredColumnLineage on parse error so it can't leak
  across a script switch (cubic P2)
- AssetGraphEdge: widen badge stacking offset 12px->18px to fully clear (cubic P3)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: resolve JOIN inputs + anchor column lineage to // materialize target

Addresses the second Codex review pass (two P1s):
- SQL inference now walks JOINed tables: build_from_maps maps every FROM entry
  AND its joins into the alias map, and single-table attribution requires no
  joins. `SELECT o.x, c.y FROM a o JOIN b c` now resolves c.y (was dropped).
- The column graph anchors a producer's lineage to its declared // materialize
  target (surfaced on the runnable node) instead of guessing a ducklake
  write-edge, which is unordered for deployed graphs and ambiguous for
  multi-output scripts. Falls back to a write-edge when no materialize target.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: gate column-lineage badge to the // materialize target write-edge

The canvas badge keyed on `e.asset_kind === 'ducklake'`, so a multi-output
producer showed the same column mapping on every ducklake write-edge. Use the
same materialize-target anchor as buildColumnGraph: the badge lands only on the
declared output's edge, falling back to the ducklake write-edge when there's no
materialize annotation. (Codex P1)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: build column trace from displayGraph so View hides draft lineage

The transitive column trace was built from graphWithDraft regardless of mode, so
in View with drafts hidden it could surface draft `// column` lineage the
deployed canvas doesn't show. Build it from `displayGraph` (the graph the canvas
actually renders) so the trace matches: draft overlays in edit / show-drafts,
deployed-only in plain View. (Codex P2)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: don't infer column lineage for local/temp staging CTAS

A CTAS into a local/temp staging table isn't the materialized output, but its
projection was inferred and (flat) column_lineage anchored to the script's
// materialize target — so staging columns showed up as the final asset's. Gate
inference to the actual output: a top-level managed-materialize SELECT, or a
CTAS/CREATE VIEW whose target resolves to a real asset. (Codex P1)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: scope inferred column lineage to one output asset

Inference accumulated columns from every output-producing query into one flat
list, all anchored (frontend) to the script's // materialize target — so an
auxiliary CTAS into a different asset showed its columns on the materialized
one. Tag each inferred entry with its output asset and, in parse_assets, scope
the list to the // materialize target (keeping untagged top-level-SELECT
entries); with no declared target, drop inference when entries span multiple
output assets rather than attribute them to an arbitrary one. Parser-internal —
no wire change. (Codex P1)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: treat CREATE TEMP TABLE/VIEW as local even under an active USE

A one-part temp name under `USE dl` resolved to an asset (ducklake://…/tmp)
before being registered local, so a final SELECT reading it invented
`final.total <- warehouse/tmp.amt` (a phantom DuckLake column) and recorded a
phantom asset. track_table_definition now registers any temporary table/view as
local up front, bypassing active-asset resolution; CreateTable/CreateView pass
their `temporary` flag. (Codex P1)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 21:08:00 +02:00
Ruben Fiszelandrubenfiszel 9172a0945b chore(main): release 1.741.0 (#9804)
* chore(main): release 1.741.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2026-06-26 21:43:02 +02:00
577ceeee86 perf(audit): re-anchor S3 audit export on enable + opt-in backfill (#9818)
* [ee] perf(audit): re-anchor S3 audit export on enable + opt-in backfill

The S3/GCS audit-log export's steady-state query filters by `age(xmin)`
(unindexable), so the only scan bound is the timestamp floor. On a fresh
enable the floor was epoch, and on a re-enable the cursor resumed from its
pre-disable position — either way the first run scanned the whole
`audit_partitioned` table. Under a `statement_timeout` (e.g. Aiven) that scan
never completes: the cursor never advances, nothing is exported, and the
repeated full scans saturate the database.

Re-anchor on enable (EE companion, windmill-ee-private#634):
- New trigger migration records a recent timestamp floor instead of the epoch
  sentinel and `DO UPDATE`s the cursor to the current snapshot xmin on
  re-enable, so the export always resumes from ~now and never rescans history.
  Includes a one-time fixup for legacy epoch-sentinel checkpoints on upgrade.

Opt-in historical backfill (new `audit_logs_s3_backfill` module + endpoints):
- Exports a chosen `[from, to)` window on demand, scanning strictly by
  `timestamp` (the partition key) in bounded keyset pages — each query is an
  index scan capped at one page (verified via EXPLAIN: later partitions
  `never executed`, ~11ms/page), so it stays well under any statement timeout
  regardless of window size. Writes alongside the steady-state objects under
  logs/audit/, without touching the xmin cursor.
- POST /settings/audit_logs_s3_backfill {from,to} (super-admin + Enterprise),
  GET /settings/audit_logs_s3_backfill_status.

Also repurposes the status endpoint's `bootstrapping` flag to mean "draining a
backlog" (the cursor is capped and catching up), and updates the setting
description to point operators at the backfill.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(audit): heartbeat backfill lease per object; bump EE ref

Address review (cubic): persist progress (refreshing the lease heartbeat) after
every object PUT in the backfill page loop, not only once per page, so the gap
between heartbeats stays well under STALE_HEARTBEAT_SECS even on slow uploads
and another replica can't re-claim mid-page and run a concurrent backfill.

Bumps ee-repo-ref.txt to pull in the EE test-race fix (folding the backlog-drain
regression into the single audit e2e test).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(audit): reject unstable backfill windows; bump EE ref

Address review (P1): the backfill keyset-pages over rows visible at scan time
and declares completion when the scan runs dry, but a row's `timestamp` is its
inserting transaction's `xact_start`. A window whose upper bound is recent or in
the future could silently omit a transaction that started inside `[from, to)`
but commits after the scan passed that timestamp. `try_start` now rejects any
`to` newer than the oldest in-flight `xact_start` (everything strictly older
than the oldest running transaction is committed and stable), using the same
trustworthy stats gating as the exporter's floor (restricted role / 2PC → a
7-day-old cutoff).

Bumps ee-repo-ref.txt for the EE monotonic-checkpoint fix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(audit): re-anchor legacy epoch checkpoints instead of synthetic floor

Address review (P1): the legacy-checkpoint fixup stamped last_oldest_inflight_ts
to now()-7d while leaving the old last_xmin in place. On an instance that
enabled export on the old code >7 days ago and got stuck before the first
successful batch, the next run would filter post-enable rows older than 7 days
out via `timestamp >= ts_floor` while still advancing last_xmin over the
interval — silently dropping them (the same floor-vs-cursor loss class fixed
elsewhere in this PR), and contradicting the "nothing committed after enabling
is skipped" guarantee.

A stuck epoch-sentinel checkpoint cannot be safely resumed (its backlog can be
arbitrarily old, so any recent floor prunes rows the cursor then skips, and an
epoch floor reintroduces the full scan). Re-anchor it to the migration's current
snapshot xmin instead — exactly like a fresh enable — so the export resumes
cleanly from ~now and the never-exported pre-upgrade window is recovered via the
opt-in backfill rather than silently dropped. Reword the setting description so
it no longer implies the disabled/legacy window is covered by the cursor.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(audit): end-to-end integration tests for the object-store backfill

The backfill previously had only SQL-level/EXPLAIN validation. Add real
integration tests (in-memory object store, sqlx::test) exercising the public
path:

- backfill_exports_window_in_pages: with the page size forced to 2 rows, a
  settled 3-day window is exported across multiple keyset pages; asserts every
  in-window row lands exactly once, rows outside [from,to) are excluded, a day
  that straddles a page boundary yields more than one object, progress counts
  match, and a re-run is idempotent (deterministic keys overwritten, no dupes).
- backfill_rejects_unstable_window: a future/live `to` is rejected as unstable,
  a window safely in the past is accepted.

Adds a test-only PAGE_ROWS override so multi-page behaviour is exercised with a
handful of rows.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(audit): note backfill scope is audit_partitioned only

Make explicit that, like the steady-state export, the backfill reads only
audit_partitioned; the pre-partitioning `audit` table is intentionally out of
scope (not a missed case).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(audit): reject backfill windows before the partitioned boundary

Address review (Codex P1): the backfill reads only audit_partitioned, but
pre-partitioning history lives in the legacy `audit` table (still read by audit
list/get via UNION ALL, and retained for the configured period — 365 days by
default on EE). Since the setting text points operators at this API for
"pre-existing history", a window overlapping legacy rows would report completion
while silently omitting them.

Per the decision to not export the legacy table, reject instead of silently
omit: try_start now rejects a `from` earlier than the oldest audit_partitioned
timestamp (every legacy row predates the partition cutover, so a `from` at/after
that boundary can never overlap them). Reworded the setting text to scope the
backfill to the partitioned era. Added a regression test, plus an RAII guard
(cubic P2) so the test-only globals are restored even if an assertion panics.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(audit): backfill object keys per-window; require trustworthy settled cutoff

Address review (two P1s):

- Object-key overwrite loss: keys were `dt=<day>/audit_backfill_<min_id>.ndjson`.
  A narrower, overlapping backfill can start a day's page at the same first row
  (same min_id) but hold fewer rows, and `put` would overwrite a broader run's
  object — silently dropping the rows only that object held. Include the
  requested window in the key so different ranges write disjoint objects (same
  window re-runs stay idempotent; consumers dedupe overlapping rows by id). New
  regression test (verified red→green).

- Untrustworthy settled cutoff: when min(xact_start) isn't trustworthy (role
  lacks pg_read_all_stats/superuser, or a prepared 2PC txn exists), the old
  now()-7d fallback could still let an old transaction commit rows inside an
  accepted window after the scan, so a "complete" backfill silently missed them.
  Since a backfill asserts completeness, reject in those cases instead of
  falling back. (The continuous exporter keeps its 7-day fallback — it only
  claims bounded lag.)

Also makes the tests robust under the parallel runner: run_backfill takes the
store as a param, so tests pass a local in-memory store (no global
OBJECT_STORE_SETTINGS race) and serialize on the PAGE_ROWS override.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(audit): reject backfill overlapping legacy table; regen deref openapi; trim migration comment

Address review (1 P1 + 2 P2):

- Empty-partition backfill (P1): the min(audit_partitioned) guard no-ops when
  audit_partitioned is empty, so an upgraded instance with legacy `audit` rows
  but no partitioned rows yet would accept a window and complete with zero rows,
  silently omitting the legacy rows. Check the legacy `audit` table directly:
  reject any window that overlaps a legacy row (subsumes the boundary check and
  covers the empty-partitioned case). Test updated accordingly.

- openapi-deref (P2): regenerate openapi-deref.yaml/json (served via include_str!)
  so /openapi.{yaml,json} expose the new backfill endpoints.

- Migration comment (P2): trim the PR-history narration to the durable
  constraints (why a recent floor and a monotonic cursor are required), per
  AGENTS.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore: update ee-repo-ref to b821fecccbcba2efed544890576bf2b84321d70d

This commit updates the EE repository reference after PR #634 was merged in windmill-ee-private.

Previous ee-repo-ref: 6b191b77aabcf77658ad4f9031576e0d7b66bf89

New ee-repo-ref: b821fecccbcba2efed544890576bf2b84321d70d

Automated by sync-ee-ref workflow.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-06-26 21:37:33 +02:00
Ruben FiszelandClaude Opus 4.8 ade74b297f feat: capture managed-materialize output schema as asset metadata (#2a) (#9812)
* feat: capture managed-materialize output schema as asset metadata (#2a)

After a managed `// materialize` run, capture the producer's output schema
via a DESCRIBE folded into the existing one-row summary read (no extra
round-trip) and persist it in a new versioned `materialized_asset_schema`
sidecar table. This is the producer-side capture that pipeline parity gap
#2b (save-time consumer-ref contract enforcement) will read back.

- materialized_asset_schema sidecar (asset-level grain), versioned: a new
  version row is inserted only when the captured column set changes.
- output_schema column added to the materialize summary codegen.
- worker extracts + records the schema on a successful materialize.
- /assets/asset_schemas read endpoint exposing the evolution history.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: address CI review on schema capture (partition col, order, status gate)

- exclude the synthetic `_wm_partition` column from the captured schema for
  partitioned assets, so the recorded contract is the producer's logical
  output, not Windmill's storage detail (claude/cubic P1).
- make the captured column list explicitly ordered (`row_number()` over the
  DESCRIBE + `list(... ORDER BY)`), so the `list()` aggregate can't reorder
  columns and spuriously bump the schema version (cubic P2).
- gate the API `record_materialization` schema upsert on a `Materialized`
  status, so a failed/running write (or a client attaching a schema to one)
  can't advance the schema history (cubic P2).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: address Codex review (manual-mode schema gate + auth contract docs)

- gate output_schema extraction on the managed (`Some((Some(_), _))`) path so a
  `// materialize manual` run — whose result is the user's own query output —
  can't persist a caller-shaped `output_schema` into materialized_asset_schema
  (Codex P2). Verified e2e: a manual run returning a fabricated
  `output_schema:[{injected,EVIL}]` records the partition but writes no schema
  version, while the managed path still captures normally.
- document the authorization contract on the new public `record_asset_schema`
  and `list_asset_schemas` helpers: they perform no access control (mirroring
  the materialized_partition siblings) and require callers to pass a
  workspace-authorized executor (Codex P1).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(frontend): schema-history tab on the ducklake asset node (#2a)

Adds a "Schema" tab to DucklakeAssetPanel surfacing the captured output-schema
versions persisted by the materialize run. Master-detail (mirrors the History
tab): the version list (newest first, newest auto-selected) shows column count +
snapshot + capture time; selecting a version renders its column/type table.
Reads the GET /assets/asset_schemas endpoint via raw fetch, matching the sibling
PartitionStatusGrid convention (these materialization endpoints are not in the
generated client).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat: schema tab is strategy-aware (history vs fixed schema)

Only a whole-table `replace` producer (CREATE OR REPLACE) can change columns
run-to-run; `append`/`merge`/partitioned writes INSERT into a fixed-schema
table, so their schema is pinned at first materialize and the "history" framing
is degenerate (always one version).

- backend: surface the managed `materialize_strategy` (`replace`/`append`/
  `merge`) on the asset-graph runnable node, alongside the existing
  `partition_kind` (same parse-from-annotation path).
- frontend: the pipeline page derives `schemaCanEvolve` for the selected asset
  from its write-producer (`replace` && not partitioned) and threads it to the
  Schema tab. Evolvable → master-detail version history; fixed → a single
  current-schema table with a short "schema is fixed" note. Unknown defaults to
  evolvable so real history is never hidden.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: schemaCanEvolve fails open on unknown producer strategy

Previously a producer present but missing `materialize_strategy` (e.g. a
draft-overlay runnable, synthesized without the field) fell through to
canEvolve=false, hiding captured history behind the fixed-schema view —
contradicting the "unknown defaults to evolvable" intent.

Now the fixed view shows only when *every* producer is a known insert-style
write (append/merge, or partitioned replace); any producer with unknown
(missing) strategy is treated as evolvable, so real history is never hidden.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 19:46:48 +02:00
centdixandClaude Opus 4.8 44c25de418 feat(ai-chat): add create_folder tool to global chat (#9819)
Global-mode chat could reference the user's existing folders in the system
prompt but had no way to create a new one, so for shared work where no
existing folder fit it would dead-end on "ask the user" or invent a
non-existent f/<folder>/… path (which fails at deploy).

- create_folder: dedicated, confirmation-gated tool for the immediate
  (non-draft) folder mutation; the creator becomes an owner. Mirrors the
  backend name validation client-side and returns a minimal { success } result.
- Folder path guidance now steers the model to create a folder only when the
  user explicitly asks for one, and otherwise to ask which folder to use for
  shared intent rather than guessing or inventing a path.
- ai_evals: in-memory create_folder mock + a create-folder case (global-path5);
  path3 maxTurns bumped to give room to ask.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 18:57:31 +02:00
GuilhemandClaude Opus 4.8 16022447c7 feat(ai-chat): surface raw apps in the @-mention context picker (#9800)
The global AI chat @-mention picker only listed flows and scripts; the
whole `app` kind was excluded, so raw (code-based) apps never appeared.
Add raw apps as a `workspace_app` reference, gated to GLOBAL mode and
filtered to `raw_app === true` so visual apps stay out.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 15:19:27 +00:00
centdixandClaude Opus 4.8 3be27521b0 feat(ai-chat): let global chat edit the user's personal instructions (#9771)
Add an update_user_instructions tool to the global-mode AI chat so the
user can ask it to remember a preference or change/stop a behavior, and
it persists the change to the user-level Global custom prompt.

- update_user_instructions tool: append a new instruction, or find/replace
  to edit/remove existing text (reuses the shared findAndReplace helper);
  enforces the 5000-char cap and echoes current text on a failed match.
- GlobalToolHelpers gains getUserInstructions/setUserInstructions; the
  manager wires them to the localStorage user-prompt store and rebuilds
  the system message so the change applies on the next chat-loop iteration.
- Render workspace vs user instructions under distinct headers in the
  global system prompt (getCustomPromptParts) so only the user block is
  presented as editable.
- Keep the tool result lean: return a short confirmation, not the full
  instructions (already re-injected into the system prompt next turn).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 15:52:16 +02:00
Ruben FiszelandClaude Opus 4.8 0ec5061270 fix: hide GCS service account key behind a reveal in object storage settings (#9815)
The GCS service account key JSON contains the secret private_key and was rendered in plain text in the settings editor on every page load (unlike S3 secret_key / Azure accessKey, which use password inputs). When a key is already configured, hide the editor behind an explicit "Show sensitive values" reveal; the editor (and thus the private_key) is only rendered on opt-in. bucket_config keeps the real key untouched while hidden, so saving round-trips correctly.

Fixes WIN-2106

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 15:08:19 +02:00
Ruben FiszelandClaude Opus 4.8 210ea3cc5a hide delete button on branchone default node (#9811)
The "Default" node of a branch-one is built with branchIndex -1 and is the
structurally-required else branch (stored separately from the branches array),
so it cannot be removed. Its delete button still rendered, and clicking it
called deleteBranch with index 0, which in removeBranch became
branches.splice(-1, 1) — destructively removing the LAST explicit branch.

Gate the delete button on branchIndex >= 0 so it only appears on explicit
branches.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 12:16:16 +02:00
hugocasaandClaude Opus 4.8 bc4d5d59c9 feat(git-sync): show webhook vs polling status on the auto-pull toggle
When a repo has an active webhook (auto_pull.webhook_id set), the status line
reads "instant via webhook"; otherwise it reads the ~1-minute polling cadence.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 12:16:08 +02:00
Ruben FiszelandClaude Opus 4.8 d04062bff5 fix: apply step timeout to 'Test this step' preview (#9810)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 11:58:38 +02:00
Ruben FiszelandClaude Opus 4.8 c3e8c789ac fix(frontend): clarify instance data table unavailable on cloud (#9806)
On Windmill Cloud the instance-level database is not supported for data
tables; users must point them at an external PostgreSQL resource. The
database-type picker previously labelled the "Instance" option only as
"Superadmin only", which is misleading on cloud where it can never be
enabled.

On cloud: disable the "Instance" option (subtitle "Not available on
cloud") and surface an info alert explaining that an external PostgreSQL
resource (e.g. Supabase, Neon) is required. Off-cloud behaviour is
unchanged.

Fixes WIN-2104

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 11:01:25 +02:00
Ruben FiszelandClaude Opus 4.8 e84df2e369 remove data pipelines link from assets page (#9809)
Removes the "Pipelines" navigation button from the assets page header
along with its now-unused NetworkIcon and base imports.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 10:47:48 +02:00
GuilhemandClaude Opus 4.8 aadfb620c0 feat(ai-chat): hint /compact in context usage tooltip (#9777)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 10:34:37 +02:00
GuilhemandClaude Opus 4.8 852bdf0295 use sidebar worker icon for runs queue indicator (#9808)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 10:34:21 +02:00
Ruben Fiszelandrubenfiszel b7a227f860 chore(main): release 1.740.0 (#9776)
* chore(main): release 1.740.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2026-06-25 21:37:57 +00:00
hugocasaandClaude Opus 4.8 5c75a2f809 feat(git-sync): poll repos and auto-pull new commits into the workspace
Phase 1 of automatic repo → Windmill sync. A monitor task (EE-licensed,
single-replica via advisory lock) git ls-remotes each auto-pull-enabled
repository ~every minute and enqueues a pull when the tracked branch moves,
reusing the {workspace_id}:git_sync concurrency key so pulls serialize with
in-flight push commits.

- windmill-store: background (no-authed) resolver get_git_repo_head_for_autopull
  that resolves the repo resource (incl. $var: refs) and ls-remotes; GitHub-App
  repos are skipped here and will sync via webhooks (phase 2).
- monitor.rs: poll/reconcile/persist with optimistic sha advance and failure
  status; targeted jsonb update so concurrent settings edits aren't clobbered.
- edit_git_sync_repository: preserve server-owned auto_pull state on UI save.
- openapi: AutoPullSettings/AutoPullMode/AutoPullStatus + auto_pull field.
- frontend: per-repo "Automatically deploy changes from Git" toggle with last
  sync status; demote the GitHub Actions link to an advanced CI option.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 19:09:36 +02:00
hugocasaandClaude Opus 4.8 b9711e5ace fix: re-pin stale-draft fork base when restoring an app deployment (#9792)
Restoring a version from an app's Deployment History sets the editor value
directly (`onRestore`) without going through `loadApp`, so the fork base
pinned for the stale-draft check is never refreshed. The restored value
carries the `parent_version` that was baked in when that older version was
deployed, so the deploy-time guard (`compareVersions`) compares an outdated
base against the current head and falsely reports the editor is "not on
latest", surfacing a spurious override/diff confirmation on deploy.

Re-pin `parent_version` to the current head on restore, mirroring the
existing seed (loadApp) and after-deploy re-pin sites. Follow-up to #9768.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 15:38:04 +00:00
Ruben FiszelandClaude Opus 4.8 6664ce6dc0 fix(frontend): apply script editor timeout to preview/Test runs (#9794)
The custom timeout configured in the script editor settings was only
honored for deployed script runs: it is persisted on the script row and
passed as custom_timeout when running by hash/path. Preview ("Test")
runs derive their timeout solely from the `timeout` query param of
/jobs/run/preview, which the editor never sent, so Test silently fell
back to the instance default.

Forward the editor's timeout setting through ScriptBuilder ->
ScriptEditor -> JobLoader.runPreview as the preview run's timeout query
param. The backend already clamps custom_timeout against the instance
max in resolve_job_timeout, so previews get the same ceiling as deployed
runs.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 15:34:54 +00:00
9d61e4e59e feat: self-host docs search for chat, mcp, cli; drop inkeep (#9772)
* feat: self-host docs search for chat, mcp and cli; remove inkeep

Embed a vendored docs snapshot (llms.txt/llms-full.txt) in the backend and
serve ranking + page rendering from GET /api/docs/{search,page}. The AI chat,
the MCP searchDocs/readDocsPage tools, and 'wmill docs' all consume it, so docs
search works with no runtime egress and is no longer EE-gated. Removes the
inkeep proxy. EE companion deletes inkeep_ee.rs (ee-repo-ref bumped).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor: name read_docs_page param `url` instead of `path`

search_docs returns each hit's `Source` URL, so the read tool now takes a
`url` argument to match — the AI/MCP loop reads "search gives a Source URL,
read takes that url" rather than copying a `Source:` URL into a `path` slot.
A bare `/docs/...` path is still accepted and canonicalized before lookup.

Regenerated openapi-deref, the MCP endpoint tools, and the frontend client.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* ci: add scheduled workflow to refresh the vendored docs snapshot

The backend embeds docs_snapshot/*.gz at build time, so the in-product docs
corpus is otherwise only as fresh as the last manual fetch.sh run. This adds a
weekly (and manually dispatchable) job that re-runs fetch.sh, sanity-checks the
result against truncation/garbage, and opens a PR via the internal app when the
snapshot changed — so a human reviews the docs diff before it rides into the
next release build.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* refactor: make docs tool-result strings caller-neutral

The search/page endpoints back three differently-named consumers (the AI chat
`read_docs_page` tool, the MCP `readDocsPage` tool, and the `wmill docs` CLI),
so the shared rendered text shouldn't name one of them. Refer to "the docs
page-reading tool" and its `url` argument instead, and add tests pinning the
caller-neutral follow-up guidance.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore: point ee-repo-ref at inkeep-removal companion rebased on EE main

The companion branch now carries only the inkeep_ee.rs deletion on top of EE
main (was based on the native-job-retry EE line, which polluted the EE PR diff).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(docs): expose docs:read in token catalog; precompute lowercased corpus

Addresses two review nits on the self-hosted docs PR:

- docs:read was enforced (ScopeDomain::Docs) but missing from the token scope
  catalog (token.rs ALL_SCOPES), so it couldn't be selected when creating a
  standard scoped token in the UI — leaving scope-restricted CLI/MCP docs use
  effectively ungrantable. Add a read-only "Documentation" group (no write
  surface) and a test asserting it is exposed.
- search ran page.body.to_lowercase() on the whole corpus per query. Lowercase
  body/title/description once at parse time (into the OnceLock corpus) and scan
  the precomputed copies instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore: update ee-repo-ref to 27a4f41b8e5603d6e444efcfc420bd1c44a07eed

This commit updates the EE repository reference after PR #630 was merged in windmill-ee-private.

Previous ee-repo-ref: c7ec3a0c2fa38d4cb5e50bf0265eef4710de4860

New ee-repo-ref: 27a4f41b8e5603d6e444efcfc420bd1c44a07eed

Automated by sync-ee-ref workflow.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-06-25 16:32:07 +02:00
hugocasaandClaude Opus 4.8 d865518934 feat: detect and guard against deploying stale drafts (#9768)
* feat: detect and guard against deploying stale drafts

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat: extend stale-draft warning to low-code app drafts

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* refactor: discard stale draft on rebase instead of resetting to latest

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat: animate AI chat thinking block open/close like tool calls

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat: detect stale flow/app drafts by pinned version at load and deploy

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: reset version-staleness state on new drafts and after app deploy

Addresses review: new-draft route reuse left stale version/draftBaseVersion (false stale-draft modal on a fresh flow/app); app deploy left parent_version pinned to the superseded base (false 'not latest' on a follow-up deploy).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 14:12:12 +00:00
Ruben FiszelandClaude Opus 4.8 12f92e3ab7 [ee] feat(backend): native script retry without one-step-flow wrapping (#9688)
* feat(backend): native script retry without one-step-flow wrapping

Schedules and data pipelines that retry a single script previously wrapped
it in a one-step flow (JobKind::SingleStepFlow), creating extra job rows, a
v2_job_status row, and UI projection complexity. This adds native retry on a
plain JobKind::Script job.

- RetrySettings: flatten Retry into a deduped retry_settings table, carried
  via the existing runnable_settings_handle (lazy, off the hot path).
- push() materializes a bare-script-with-retry SingleStepFlow into a native
  Script job (gated on min-version + no handlers/retry_if).
- add_completed_job re-pushes the next attempt on failure with backoff,
  tracking the attempt counter in v2_job_queue.extras and the chain via
  parent_job; schedule completion handlers fire only on the terminal attempt.
- frontend: ScriptRetryChain shows the attempt chain on the run page.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(backend): native retry_if eval + per-occurrence schedule handlers

Extends native script retry to the two cases that previously stayed on the
one-step-flow path:

- retry_if: evaluated natively on the failure path via a feature-gated
  windmill-jseval dep (quickjs) over the failure result + flow_input; push
  materializes such policies natively only when quickjs is available.
- on_failure_times / on_recovery: apply_schedule_handlers now resolves each
  past scheduled occurrence's terminal status across its native-retry chain
  (root OR any parent_job=root child succeeded) and excludes the current
  occurrence, so the counting is per-occurrence rather than per-attempt.

All scheduled-script retries now go native (schedule.rs gate removed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(backend): always materialize retry_if natively; unsupported without quickjs

retry_if is evaluated by the worker (which always has quickjs), not the
pusher, so gating materialization on the pusher's feature was wrong. The
flow path was never a real fallback either — the flow runtime needs quickjs
to evaluate retry_if too. retry_if now always goes native; on a worker
without quickjs it is unsupported and fails closed (no retry).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(backend): un-park asset-cascade (pipeline) retry

Native retry resolves the blocker that parked pipeline retry: a retried
subscriber is now a Script job (not a one-step flow / flow step), so it
stays eligible for asset dispatch and can trigger its own downstream on
recovery.

- scripts.rs: persist // retry <count> [<delay>] to script_trigger on asset
  edges (was dropped with a TODO warning).
- asset_dispatch.rs: is_eligible_kind keys off flow_step_id, not parent_job,
  so native-retry attempts dispatch on success while flow steps stay excluded.
- tests: retry-bearing subscriber now dispatches as a native Script carrying
  the policy in runnable_settings_handle; native-retry attempt is eligible.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(backend): cap native retry interval, lazy result serialization, idempotent retry push

Hardening from a self-review of the native retry path:
- Cap the backoff at MAX_RETRY_INTERVAL to match the flow-runtime path
  (evaluate_retry); the exponential formula could otherwise schedule up to
  ~18h vs the flow path's 6h.
- Serialize the failure result lazily (only when a retry_if policy needs it),
  so the common failure no longer pays the serialization on the failure path.
- Push each retry with a deterministic id per (root, attempt). If a worker
  dies between enqueueing the retry and finalizing the current attempt, the
  reaper re-handles the attempt and lands here again — push rejects the
  duplicate id, so the retry is enqueued exactly once (no double-retry).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(backend): defer schedule handlers idempotently on retry-push replay (review P1)

Address local-review findings:
- P1: retry_pending was derived from the retry push *result*, so on a worker
  crash + reaper replay the duplicate-id push returned Err → retry_pending
  flipped to false → apply_schedule_handlers fired for the non-terminal
  attempt (and the terminal attempt later fired them again). Pre-check whether
  the deterministic retry id already exists and report it as pending without
  re-pushing, so the handler-deferral invariant is crash-idempotent too.
- P2: refresh the stale 'wrap the script in a one-step flow' comment in the
  asset-cascade retry push — it now materializes a native Script.
- Add RetrySettings <-> Retry round-trip unit tests (clamping edges).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(backend): native retry chain + per-occurrence status sqlx tests

Close the two integration-test gaps flagged in local review:
- chains_attempts_and_is_idempotent: drives maybe_enqueue_native_script_retry
  through attempt0 -> retry1 -> retry2 -> exhausted (counter, backoff, max-attempts)
  and asserts crash-replay idempotency (the P1 fix: a replayed completion reports
  pending without double-enqueueing).
- per_occurrence_status_counts_recovered_as_success: pins the exact per-occurrence
  terminal-status query from jobs_ee::apply_schedule_handlers — a retried-but-
  recovered occurrence counts as success, retries (parent_job set) are excluded
  from occurrence counting, and the current occurrence is excluded.
- canceled_job_does_not_retry: cancellation wins over a pending retry.

Runtime sqlx API (no .sqlx cache entry needed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(frontend): exclude schedule handlers from the retry-attempt chain

The retry chain listed all script children of the root by parent_job, but
schedule completion handlers (on_failure/on_recovery/on_success) are also
script children — when the occurrence has no retries, the handler's parent is
the root itself, so a successful, never-retried job rendered a bogus
'Retries (1)' badge pointing at the handler. Filter children to re-runs of the
same script (matching script_hash); real retries keep the root's hash, handlers
run a different script.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(frontend): surface schedule handlers on the run page

Extend the run-page chain component with schedule completion handlers:
- A 'Handlers' row on a scheduled job links to the on_failure/on_recovery/
  on_success runs that fired for that occurrence (found as children of the
  terminal attempt, identified by their synthetic created_by).
- A handler's own run page now shows a 'Failure/Recovery/Success handler'
  label with a link back to the run it handled and its schedule. on_recovery
  and on_success share created_by, disambiguated by the recovery-only
  error_started_at arg.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(backend): restore folder_default_permissioned_as sqlx caches dropped by prepare

An earlier `cargo sqlx prepare` on this branch ran before #8801's
folder_default_permissioned_as test merged in, so it pruned the 3 query caches
that test needs; cargo_test then failed under SQLX_OFFLINE. Restore them from main.

* fix(backend): only cascade assets from native retry attempts, not handlers (review P1)

is_eligible_kind keyed dispatch on flow_step_id alone, so every parented Script
child became asset-eligible — including schedule/error/recovery handlers (Script
jobs with parent_job set and no flow_step_id). A handler that declares assets
would then trigger a cascade the old parent_job IS NULL guard prevented. Gate
parented jobs on being a genuine retry attempt: a re-run of the SAME runnable as
its chain parent (handlers run a different script). Runtime query, no sqlx cache.

* fix(backend): cache the private-gated retry_setting asset-dispatch test query

The same prepare-without-private that dropped the folder_default caches also
pruned the cache for the retry_setting_dispatches_subscriber_as_native_script
test query (asset_trigger_dispatch.rs:721). Regenerated with --features private.

* fix(backend): exclude handler children from per-occurrence recovery (review)

A scheduled occurrence's on_failure/on_success handler runs as a successful
child (parent_job = occurrence), and the per-occurrence success EXISTS counted
ANY successful child — so a failed occurrence whose error handler succeeded was
marked 'recovered', breaking on_recovery (test_script/flow_schedule_handlers in
the merge) and on_failure_times counting. EE query now scopes the EXISTS to
same-runnable children (only native retry attempts); regenerate sqlx cache + bump
ee-repo-ref. native_retry_test gains a handler-child regression case.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(backend): scheduled-script retry is a native Script, not SingleStepFlow

test_push_script_with_retry / test_try_schedule_with_retry (from main) asserted
the old SingleStepFlow wrapping for scheduled-script retry; this PR makes it a
native Script. Update both to assert kind='script' and that the retry policy is
carried via runnable_settings_handle.

* fix(backend): preserve dedicated_worker on native retry + saturate count casts (cubic)

Address cubic CI review:
- P1: the SingleStepFlow->native Script materialization dropped dedicated_worker,
  so a dedicated-worker scheduled script lost its dedicated pool on retry. Resolve
  it from the script row in push so the materialized Script keeps the dedicated tag.
- P2: saturate the u32->i32 retry-attempt narrowings (RetrySettings::from) and the
  u32->i16 // retry count narrowing (scripts.rs) instead of wrapping.

* fix(backend): use a retry-specific signal, not runnable equality (codex review)

Address Codex CI review:
- P1: is_native_retry_attempt treated any same-runnable parented Script child as
  a retry. WAC v2 inline children have that exact shape, so an inline child of an
  asset producer would cascade. Use a retry-specific signal instead: the job
  carries a retry_settings policy (always re-inserted by maybe_enqueue) and has no
  flow_innermost_root_job. Apply the same flow_innermost guard to the EE
  per-occurrence EXISTS (WAC inline children must not count as a recovery).
- P1: the deterministic retry-id pre-check raced with push; a concurrent duplicate
  now resolves as 'retry pending' (re-check on the duplicate-id error) instead of
  flipping retry_pending to false and firing handlers early.
- Tests: native_retry + asset_trigger_dispatch gain WAC-inline-child cases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(backend): explicit native_retry_attempt marker, drop heuristics

Replace the per-site "is this a retry?" inference (parent_job + runnable match +
flow_innermost / retry_settings) with one explicit marker: a sparse
native_retry_attempt(job_id, attempt) table, written in maybe_enqueue. The marker
also carries the attempt counter (previously in v2_job_queue.extras), so it's the
single source of truth.

- asset_dispatch: is_native_retry_attempt is now one indexed EXISTS on the marker.
- EE per-occurrence query: joins the marker instead of guessing by runnable/flow_innermost.
- maybe_enqueue: reads/writes the marker (persistent) instead of queue extras.
- Lifecycle: swept with the job in retention (log_cleanup), no FK to keep bulk delete cheap.
- Eliminates handler / WAC-inline-child misclassification by construction.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(backend): sweep native_retry_attempt markers in the periodic retention path too (codex)

The marker has no FK and relies on retention cleanup; log_cleanup.rs swept it but
the periodic monitor.rs path deleted v2_job rows without it, orphaning markers.
Add the same WHERE job_id = ANY(...) sweep there.

* fix(backend): widen native_retry_attempt.attempt to integer (cubic)

The smallint column was cast to/from u32 and could wrap a retry chain longer than
i16::MAX into premature exhaustion. Use integer, matching the retry policy's i32
attempt count, so no narrowing occurs on the maybe_enqueue read/write path.

* feat(frontend): mark retries via is_retry on listJobs; drop SAVEPOINT

- Expose an is_retry flag on jobs (UnifiedJob/CompletedJob/QueuedJob + openapi),
  computed from the native_retry_attempt marker. The run-page chain now filters
  retry attempts by is_retry instead of the script_hash heuristic, so WAC v2
  inline children (same script, parent_job) no longer render as retries (codex).
- Revert the marker-cleanup SAVEPOINT (an unused pattern in this codebase): keep
  the plain catch-and-continue matching the other side-table deletes; the table is
  created by a startup migration so it always exists when cleanup runs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(backend): mark is_retry sqlx(default) so non-list job queries can omit it

The single-job GET query maps directly to CompletedJob/QueuedJob via FromRow but
does not select is_retry, which errored with "no column found". Only the list
endpoint populates the marker; #[sqlx(default)] lets every other query omit the
column and default to None.

* feat(backend): select is_retry in single-job GET too for consistency

The list endpoint already exposes the marker; populate it on the single-job GET
(both completed and queued variants) as well so a run loaded directly reflects
its retry status. #[sqlx(default)] stays as a safety net for any other query.

* feat(backend): reap orphaned native_retry_attempt markers via periodic sweep

The marker has no FK to v2_job (to keep the hot bulk retention delete cheap), so
direct job deletions (workspace/job delete, schedule clearing) would leave marker
rows orphaned. Rather than add explicit cleanup to every v2_job delete site (which
must then be remembered for every future path), reap orphans in the periodic
delete_expired_items pass: DELETE FROM native_retry_attempt WHERE NOT EXISTS (the
job). The table is sparse so the anti-join drives off it and probes v2_job by PK —
cheap. Retention still sweeps markers inline (keeps the table small so this stays
cheap); a transient orphan is harmless (nothing reads is_retry for a gone job).

* fix(frontend): include flow handlers in retry chain handler row (codex)

Schedule on_failure/on_recovery/on_success handlers can be flow paths (flow/...),
whose handler job is a flow, not a script. The chain fetched children with
jobKinds:'script', hiding flow handlers. Drop the kind filter — retry attempts
are still selected by is_retry and handlers by created_by, so both kinds surface.

* fix(backend): carry concurrency/debouncing settings into native retries

maybe_enqueue re-pushed the next attempt with ConcurrencySettings/DebouncingSettings
::default(), dropping the script/pipeline concurrency settings the failed job carried
in its runnable_settings_handle. A retry of a concurrency-limited script then inserted
no concurrency_key and ran unbounded. Resolve both from the same handle (cached) and
pass them in the payload, which push forwards to the materialized retry. Adds a
regression test asserting the retry's handle resolves to the concurrency settings.

* fix(backend): carry concurrency/debounce into scheduled-retry root + document retry-helper auth (codex)

P1a (schedule.rs): the scheduled-retry materialization fetched the script's
concurrency/debounce settings but passed ConcurrencySettings/DebouncingSettings
::default() into the SingleStepFlow payload, so the root attempt's handle held only
the retry policy and the whole chain ran unbounded. Pass the fetched settings.
Regression test asserts the root handle resolves to retry + concurrency.

P1b (jobs.rs): document maybe_enqueue_native_script_retry's authorization contract
— it is pub only for the integration test; the sole production caller is the worker
completion path passing a DB-derived, already-authorized MiniCompletedJob.

* docs(backend): attach native-retry auth contract to the function itself (codex)

The doc block was merged with eval_retry_if's doc and bound to that function,
leaving maybe_enqueue_native_script_retry undocumented. Split them: eval_retry_if
keeps its own doc; the native-retry + authorization contract now sits directly
above maybe_enqueue_native_script_retry.

* docs(backend): regenerate served openapi-deref with is_retry + fix stale comments (codex)

- Regenerate openapi-deref.{yaml,json} (served from lib.rs): they were stale since
  1.734.0 and lacked is_retry on QueuedJob/CompletedJob, so clients reading the
  served spec couldn't see the field. Now current at 1.739.0.
- schedule.rs: a retry_if gate is evaluated at failure time and fails closed without
  quickjs (no retry); it does not fall back to a flow path.
- windmill-types jobs.rs: is_retry is selected by both the list and single-job GET
  endpoints (not list-only).

* docs(backend): fix remaining stale retry_if/quickjs comments (codex)

The retry_if block and the push materialization comments claimed push keeps
retry_if on a flow path / the worker always has quickjs. The code always
materializes native retry and the no-quickjs eval_retry_if path fails closed —
correct the comments to that constraint.

* docs(backend): fix stale quickjs-fallback + schedule-handler-restriction comments (codex)

- Cargo.toml quickjs feature: without quickjs a retry_if gate cannot be evaluated
  and the job does not retry (no one-step-flow fallback).
- jobs.rs handler-defer comment: apply_schedule_handlers resolves per-occurrence
  failure/recovery status across the retry chain, so the old 'restricted to
  schedules whose handlers don't need per-occurrence counting' claim is dropped.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 13:22:26 +00:00
Ruben FiszelandClaude Opus 4.8 248540ac4d feat: bounded-cascade selective execution for pipelines (UI + CLI) (#9695)
* feat: bounded-cascade selective execution for pipelines (UI + CLI)

Run a prefix of a pipeline cascade: from a schedule/manual root, fan
downstream but stop at chosen end node(s) — the path-between set over the
asset-graph lineage DAG. Exposed as a canvas 'Run downstream up to…' pick
mode and a 'wmill pipeline run <folder> --to' CLI command. No backend or
parser changes; reads the existing graph, tags, and triggers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat: surface bounded-run on the run caret, trigger-node kebab, and Test button

Move 'Run downstream up to…' from the runnable kebab onto the play-button
caret popover (Edit mode, next to Run / Run + trigger N downstream); add it
to the trigger-node kebab so schedule/data_upload entrypoints expose it on
the View page; and to the ScriptEditor Test split caret for the open script.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: address CI review on bounded-cascade (cubic)

- Port CLI engine test from Deno to bun:test under cli/test/ (won't run under bun test otherwise).
- closure() now excludes the start node on a cycle back to it (descendants/ancestors contract); regression tests both engines.
- CLI 'pipeline run --to' rejects unresolved/ambiguous end tokens instead of silently running a different subset.
- Sort a copy in the runSelection order test so the launch-order assertions aren't invalidated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: address standing review nits on bounded-cascade

Resolves the four recurring P1/P2 findings from the codex/pi/claude
reviews:

- UI gate (P1): the canvas/trigger-node "Run downstream up to…"
  affordance was gated on the subscriber-only downstream map, so a valid
  start whose only downstream is a pure reader had a non-empty bounded
  set but no menu entry. Gate on the read-aware lineage downstream
  (buildLineageDownstreamMap), matching the bounded engine.
- waitJob (CLI): a completed job without explicit success:true now
  counts as a failure, mirroring the frontend waitJobTerminal — the
  cascade only advances on a confirmed success.
- Comment fix (CLI): the unbounded `run` path uses the read-aware
  lineage DAG (pure readers included); dropped the false "parity with
  the canvas cascade" (subscriber-only) claim.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: expose bounded-run caret for pure-reader-only starts (codex P1)

The canvas wiring from the prior commit passed `onStartBoundedRun` from
the read-aware lineage map, but the leaf components still hid the popover
that holds the "Run downstream up to…" action behind a subscriber-only
gate:

- RunnableNode rendered the Run-button caret only when
  `hasCascade = downstreamCount > 0` (subscriber-only). A valid start
  whose only downstream is a pure reader got `onStartBoundedRun` but no
  visible action. Now the caret opens when there's a cascade OR a
  bounded-run start (`hasCaret`), and the "Run + trigger N downstream"
  item is gated on `hasCascade` so it never reads "trigger 0".
- ScriptEditor's Test split button activated only when
  `downstreamSubscribers > 0`, falling through to a plain Test button
  (no caret) otherwise. Now it also activates when `onBoundedRun` is
  set, with the "Test + trigger N" item gated on the count.

For a manual root (no trigger-node kebab fallback) with a pure-reader
downstream this was the only UI entry point, so it was previously
unreachable. Verified in-browser: a manual-root script writing an asset
read-only downstream now exposes "Run downstream up to…" on the
ScriptEditor Test caret with the cascade item hidden.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: gate ScriptEditor bounded-run on read-aware downstream; fix CLI asset-end warning (codex P2)

- Details-pane (ScriptEditor) bounded-run entry was gated only on
  `validStartPaths`, broader than the canvas which also requires
  read-aware downstream (`hasLineageDownstream`). An isolated start could
  thus expose "Run downstream up to…" and enter pick mode with no
  selectable end. Now gated on `lineageDownstreamPaths` (script paths with
  a downstream in `buildLineageDownstreamMap`), matching the canvas.
- CLI dropped-end warning called `scriptPathOf(d)` unconditionally, which
  slices `script:`-length chars off an asset id too — `datatable:main/raw`
  printed as `le:main/raw`. Now prefix-checks like the JSON output.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: correct --from error to exclude only row-backed event triggers (codex P2)

The bounded-start validation message listed `kafka/webhook/…` as event
triggers that can't start a bounded run, but webhook/data_upload are
rowless and read as manual roots (valid starts). Only the row-backed
native kinds (kafka/mqtt/nats/postgres/sqs/gcp/email — EVENT_TRIGGER_KINDS)
are excluded; the message now names those.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: surface dropped ends in CLI JSON; disambiguate shared-trigger bounded start (codex P2)

- CLI `run --json` silenced the dropped-end warning, and the JSON payload
  echoed the originally-resolved `--to` list with no reachable/dropped
  split — a resolved-but-unreachable end looked like a clean plan that
  silently runs only the start. JSON now includes `reachableEnds` and
  `droppedEnds` (shared `idLabel` helper, asset-id safe).
- Trigger nodes dedupe per (kind, ref), so a schedule shared across
  scripts collapses to one node, but `recordSourceTrigger` kept only the
  first target path — the bounded-run action then rooted at an arbitrary
  script (or hid when only that first script lacked downstream). Now all
  target paths are tracked and the action is offered only when exactly one
  is a valid start with downstream; multi-eligible nodes suppress it
  rather than guess.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: don't run hidden drafts in View-mode bounded cascade (codex P1)

launchCascadeScript unconditionally preferred drafts.get(path) over the
deployed script. In View mode with drafts hidden (displayGraph is
deployed-only), a bounded run started from a trigger-node kebab would
execute preview jobs from hidden local draft content instead of the
deployed scripts the user is looking at.

Gate draft execution on `mode === 'edit' || includeDrafts` — the exact
condition under which displayGraph includes drafts — so execution always
matches the displayed graph. No-op for scripts without a draft; the
edit-mode "Run + trigger N downstream" cascade is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 12:08:05 +02:00
Diego ImbertandClaude Opus 4.8 170cd79aaf fix: allow hyphens in postgresql database name validation (#9782)
* fix: allow hyphens in postgresql database name validation

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test: cover hyphen acceptance in validate_dbname

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 12:02:35 +02:00
Ruben FiszelandClaude Opus 4.8 74ebfc67f0 fix(frontend): nested-loop "Test this step" resolves iter to innermost loop (#9778)
* fix(frontend): nested-loop "Test this step" resolves iter to innermost loop

In a loop-inside-a-loop, the inner step's "Test this step" tab prefilled
its arguments using the outermost ancestor as the parent module, so
flow_input.iter resolved to the parent loop's iteration value instead of
the inner loop's.

dfs(id, flow, true) returns [step, immediate parent, ..., root], so
modules[modules.length - 1] is the outermost ancestor. The prop picker
needs the immediate parent (modules[1]) so getFlowInput resolves iter at
the innermost loop's level. A single loop was unaffected because both
indices coincide; only depth >= 2 broke.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(frontend): nested-loop parent selection for test-step args

Pins that modules[1] from dfs(stepId, flow, true) is the immediate parent
for every step across all container types (for/while loops, branchone,
branchall, aiagent tools) and nesting depths, and that getStepPropPicker
then resolves flow_input.iter to the innermost enclosing loop.

Covers >400 step positions across 107 generated flow shapes, plus explicit
single/nested/while/branch iter-resolution cases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(frontend): remove nested-loop parent selection test

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 10:19:55 +02:00
Ruben FiszelandClaude Opus 4.8 f6998ec54c feat: data tests for ducklake pipeline materialization (#9708)
* feat: data tests for ducklake pipeline materialization

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(frontend): data_test count badge on pipeline graph nodes

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat: surface annotation badges (incl. data_test) on deployed pipeline nodes

Backend graph endpoint now parses each pipeline member's deployed body and returns partition/freshness/tag/retry/data_test, so badges render on deployed nodes, not only live drafts. Aligns the TS DataTest.relationships fields to snake_case to match the Rust serde wire shape (the type is now populated from both the parser and the backend JSON).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(frontend): keep materialize output edge when editing the producer in the pipeline graph

The live-edit overlay re-derived a selected/edited script's lineage from // on inputs + body-inferred assets only, so the // materialize <asset> output (an annotation, not body SQL) was judged stale and its write-edge dropped on select — leaving the materialized asset unlinked (and the node's annotation badges hidden). Include the parsed materialize target in liveRefKeys and the draft writeOuts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat: run all data tests in one pass with a structured per-test result

Replace the raise-on-first-violation probes with a single materialize summary that embeds every test's violating-row count in a data_tests column (computed in a CTE, since DuckDB rejects subqueries inside struct literals). The worker reads the breakdown and decides pass/fail: a clean run returns the per-test summary in the result; a failing run errors with the FULL list (every test, ✓/✗ + counts), not just the first failure. Verified live (EE) for built-ins + custom, pass and multi-failure.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(frontend): data-test pass/fail checklist in the job result

DisplayResult renders a per-test checklist (✓/✗ + violation counts) above the raw result for managed materialize runs — from the structured data_tests on success, and parsed from the worker's breakdown message on failure. Shows in the script editor Test panel, the runs page, and the pipeline asset run pane.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(frontend): move data-test badge onto the producer→asset edge with run status

The test badge now sits on the write-edge (the transformation link) rather than the producer node, since the tests assert on what the transformation produces. It's tinted by the producer's last-run status (green = passed, red = a test failed) and its hover title lists every declared test. Removes the now-redundant node badge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(frontend): render custom data-test scripts as their own clickable graph nodes

A // data_test <script_path> custom test now appears as its own node below the asset it validates, joined by a dashed 'tests' edge. Clicking it opens the test script in the detail pane (dispatched like any runnable). Built-in tests stay folded into the edge badge; only script-backed tests become nodes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(frontend): type data-test edge field via AssetGraphResponse, not in-scope g

BuiltEdge is declared at component scope, outside build(g), so referencing typeof g.runnables in its type failed CI's svelte-check (Cannot find name 'g'). Use the imported AssetGraphResponse type instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(frontend): anchor edge badge on routed path + a11y text on test icons

Address review: the data-test edge badge anchored on the straight-line midpoint, floating off detoured edges — anchor it at detourX when the edge is routed through a gutter lane. Add sr-only pass/fail text so the checklist icons are distinguishable to screen readers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: close data-test enforcement bypass + gate badges to scripts + reject multi-stmt custom tests

Address review (cubic) findings:
- P1: managed materialize generates its own summary row carrying data_tests, and enforcement reads that column — but a // result_collection annotation (e.g. a scalar mode) could reshape the row and drop data_tests, silently bypassing a failing test. Force LastStatementAllRows for managed materialize runs so the summary row is always intact.
- P2: asset-graph annotation badges were keyed by path only, so a flow sharing a path with a pipeline script inherited its badges. Gate the lookup on usage_kind == Script.
- P2: a custom test body is embedded as a subquery, so a multi-statement body produced invalid SQL with an opaque DuckDB error. Validate single-statement up front with an actionable error; align docs/comments.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: fail loud if fewer data-test outcomes recovered than declared

Defense-in-depth from the fresh-context review: enforcement reads per-test outcomes off the materialize summary row, but if the data_tests column were ever dropped/reshaped at the FFI boundary, extract_data_tests would return fewer (or zero) outcomes and the run would silently pass unverified tests. Track the embedded test count on MaterializeExec and abort with a clear error when recovered < declared. Verified: normal run (4==4) unaffected; the scalar-result_collection bypass already fails.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: relationships data test same-lake reuse + schema-qualified target quoting

Address Codex/Pi review (two P1s in the relationships codegen):
- A relationship into the same ducklake as the materialize target minted a second ATTACH of that lake under _wm_ref_N while _wm_target already held it — DuckDB forbids attaching one database twice, so the test failed before it could run. Reuse _wm_target for same-lake references.
- A schema-qualified target (ducklake://warehouse/main.dim_products.sku) emitted FROM _wm_ref_0."main.dim_products" — one quoted identifier with a literal dot — silently querying a nonexistent table. Quote each dotted segment so the dot stays a schema separator.
Adds tests for both.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(frontend): refresh data_test badge on deployed-script drafts + scope to materialize target

Address Codex review nits (both P2):
- resolveGraph: the existing-runnable draft-overlay branch kept the deployed data_tests, so adding/removing // data_test lines on an already-deployed script left the badge stale until redeploy. Refresh it from the live parse like the new-runnable branch.
- AssetGraphCanvas: data tests were attached to every write-edge from a producer. They assert on the // materialize target (always a ducklake asset in v1), so only the ducklake write-edge now carries the badge and custom-test nodes — a producer's other (S3/datatable) outputs no longer show them.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 23:47:02 +02:00
Ruben FiszelandClaude Opus 4.8 d131d754e1 feat: ducklake time-travel UX (snapshot history + AT VERSION reads) (#9709)
* feat: ducklake time-travel UX (snapshot history + AT VERSION reads)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: catalog-qualify ducklake time-travel FROM hints (lake. prefix)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: render ducklake snapshot_time (microseconds since epoch) correctly

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor: merge ducklake History + Query into one master-detail tab

Snapshot list (left) selects the version previewed in the read-only grid (right); newest auto-selected. Copy-clause moved to the preview's SQL line.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: scope ducklake snapshot history to the table's versions

Catalog-wide snapshots predate a table's creation; previewing AT a version before the table existed errored ("Table ... does not exist at version N"). The DUCKLAKE_SNAPSHOTS marker now takes the table and lists only snapshots from its first creation onward. Also: narrower snapshot-list pane on large screens (target a fixed width, not a fixed fraction).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: load ducklake preview columns at the pinned version + reset on asset switch

Addresses CI review (codex/pi P1, cubic P2):
- Historical previews loaded current-schema columns, so an AT(VERSION) read enumerating a column added in a later snapshot failed. Now DESCRIBE-loads the column set at the pinned version; the read is gated on columns matching the current version to avoid a stale-colDefs race on version switch.
- selectedVersion no longer sticks across assets: the panel is keyed on path (remounts per asset) and effectiveVersion falls back to newest when the pick isn't in the current list.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: match History tab UI (master-detail, full-FROM copy) after merge

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: handle catalog-only ducklake asset paths (no table segment)

parseDbInputFromAssetSyntax threw on a catalog-only path like 'ducklake://main' (undefined.split('.')) — a real graph node (e.g. a consumer of the whole catalog). It now returns a table-less input instead of throwing, and DucklakeAssetPanel renders only the partition grid (no per-table history/time-travel) for table-less nodes. Adds parser unit tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: escape ducklake catalog name in client-built time-travel DESCRIBE

fetchDucklakeColumnsAtVersion interpolated the catalog name into an ATTACH string literal without escaping; double single-quotes (mirrors backend escape_sql_literal) so a quote-containing catalog name can't break out. Also fixed the v1.x docs checklist line to match the shipped full-FROM copy affordance.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 23:11:47 +02:00
Ruben Fiszelandrubenfiszel 920f5688ca chore(main): release 1.739.0 (#9746)
* chore(main): release 1.739.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2026-06-24 18:01:19 +00:00