Resolves npm ci EUSAGE failure: the optional cpu:wasm32 @rolldown/binding-wasm32-wasi
declares deps on @emnapi/core@1.11.2 and @emnapi/runtime@1.11.2 that had no resolved
lockfile entries.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(frontend): chat-scoped unified session changes bar
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(frontend): drop diff-baseline toggle, show natural per-row diffs
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): carry Draft marker to expanded raw-app file rows
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): show raw-app Draft badge once at tree root, not per file
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(frontend): reuse shared DraftBadge in session diff drawer
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(frontend): show draft-author avatars in session diff badge, icon-only in sidebar
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): keep badge pill around avatar in icon-only DraftBadge
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): small draft marker = indigo pen + avatar; correct itemKind label
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(frontend): drop package-lock churn from merge (match origin/main)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): dedup diff-button count for legacy fork sessions; test mask helper
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(frontend): collapsible session diff panel + per-row open-diff action
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(frontend): shared sessionDeployModel for review & deploy (S1)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(frontend): model-driven session review drawer, deploy inert (S2)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(frontend): wire session deploy + on-behalf/conflict gating (S3)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(frontend): behind banner + Update fork + deployment request (S4)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(frontend): session changes dock opens drawer by filter (S5)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): session review UI polish (badge, dock, In parent, tree width)
- draft rows show only the avatar DraftBadge, not a duplicate state pill
- drop redundant dock Review button (same as "N to review")
- rename Done -> In parent with a "deployed in parent workspace" tooltip
- widen the file tree
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): existence-gate In parent rows; Badge filters; badge hover
- drop discarded mask-only items from the In-parent segment (existence check)
- use the Badge component for the drawer filter segments and the changes dock
- soften the blue Badge hover (blue-50 base was jumping to blue-200)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(frontend): polish session diff drawer (layout, badges, actions)
- remove empty fork-banner gap; uniform sidebar tree padding
- full-bleed diff list: drop card borders/side padding, separators between items
- clamp tree x-overflow; right-align tree badges (min-w-0 on the row button)
- brand-compliant selected filter badges; smaller draft badge
- hide per-row open-diff button when the panel is open
- rename "Delete draft" to "Discard draft" (destructive); remove header Review button
- larger sm deploy/discard action buttons; remove per-item diff-content collapse
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(frontend): session Edits dock — deploy gating + change-op tracking
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(frontend): session bar per-status badges; drop change-op tracking
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(frontend): unwrap raw apps into per-file tree in session diff sidebar
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(frontend): dot-parcours pipeline (badge-derived, melt tooltip) + discard confirm
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(frontend): behind-only session item reads as deployed, not bare
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* style(frontend): even sidebar tree margins; gutter-aware right padding
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(frontend): pass chat id as from_session; wire deploying flag
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(frontend): session diff drawer scroll-to-flush, ordering, spacer, deploy gating
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): preserve chat mask on compact; guard stale existence checks; clear poll timers
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(frontend): refresh bar after drawer deploys; conflict hint over chip; plain conflict badge
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* style(frontend): diff drawer card layout with flash ring and aligned insets
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(frontend): hide stale deployed chip once row status badge reads deployed
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(frontend): session dock to two states; drop parent deploy
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(frontend): staged deploy animation in session edits drawer
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(frontend): stale-draft warning in session edits drawer
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(frontend): keep chat mask honest on deploy and discard
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(frontend): map trigger_email deploy kind; serialize mask persists
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(frontend): reset mask on new chat; close review-flagged races
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* style(frontend): rename session drawer title to Edited during session
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(frontend): keep mask persist queue alive after a failed save
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(frontend): sync session chatId on chat rotation; gate deploy on canWrite
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(frontend): keep compare handoff for deletion-only session edits
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(frontend): hold deploy success beat across re-keyed rows
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The `duckdb` bundled feature compiles the whole DuckDB C++ library from
source (~2min), which dominates the FFI crate's build. A fresh git
worktree had an empty target dir and paid that cost every time.
build_dev.sh now builds into a per-user cache shared across worktrees,
keyed by Cargo.lock + build.rs so distinct DuckDB versions don't collide.
Uncommitted changes to the crate source fall back to an isolated
per-worktree ./target so active FFI development neither disturbs nor is
disturbed by the shared cache. Add a crate .gitignore for /target, and
note the shared cache in the AGENTS.md / backend CLAUDE.md build steps.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: resolve extensionless bun relative imports on windows loader
* fix: resolve local module probe against importer dir not job root
* fix: keep node_modules-internal relative imports out of windmill resolver on windows
* fix(cli): emit HD-1 test_edges + HD-2 scd2 _current write in --local pipeline graph
Close the remaining local-vs-deployed graph parity gaps in `wmill pipeline
show <folder> --local` so it matches the deployed graph (backend
`asset_graph`, windmill-api-assets):
- HD-1 `test_edges`: synthesize ordering-only producer → tested-script edges
from parsed `// data_test` annotations. A `relationships` test references
its `to_path` asset; a custom `// data_test <script>` resolves best-effort
against that script's parsed reads. Each referenced asset is resolved to its
in-pipeline producer via the write edges; self-edges and producer-less
(external) assets are dropped — mirroring the backend set semantics.
Routed through the asset node in boundedCascade's lineage DAG (asset →
tested script) so a cold/bounded cascade orders the referenced dimension
first, matching the frontend.
- HD-2 scd2 `<dim>_current` companion write: a managed `// materialize …
history` (scd2 && !manual) also produces a `<dim>_current` view. Register it
as a second write edge and mark the asset `derived_from` its base dimension,
so a consumer reading only the view links back to the producer instead of
orphaning. Gated exactly like the backend `MaterializeSpec::write_targets` /
`scd2_current_target`.
The pinned `windmill-parser-wasm-asset` (1.740.0) predates the `scd2`
materialize flag, so `buildLocalPipelineGraph` takes an injectable parser and
the HD-2 test injects one that re-adds `scd2` for a `history` materialize —
exercising the already-shipped companion-write branch until a wasm carrying
`scd2` is republished (cf. #9926).
Extends cli/test/pipeline_local_graph_unit.test.ts with HD-1 (relationships,
no-producer, self-test, custom) and HD-2 coverage.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(cli): pin windmill-parser-wasm-asset 1.749.0, drop HD-2 test parser seam
Now that windmill-parser-wasm-asset 1.749.0 (which serializes the `scd2`
materialize flag) is published, bump the CLI pin and retire the temporary
injection seam:
- Remove the `infer?` parameter from `buildLocalPipelineGraph`; it always uses
the wasm-backed `inferScriptAssets` again.
- The HD-2 `<dim>_current` companion-write test drives the real wasm directly
(drops the `inferWithScd2` wrapper that re-added `scd2` against the pinned
1.740.0 build).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(frontend): pin windmill-parser-wasm-asset 1.749.0 to match CLI
Restore the CLI↔frontend lockstep on the asset parser wasm broken by the
previous commit: every other windmill-parser-wasm-* package is pinned to the
same version in both cli/package.json and frontend/package.json, so keep the
asset parser aligned too. The frontend derives materialize/scd2 from its own
TS annotation parser (`parsePipelineAnnotations`), so this bump only affects
body asset inference in the live graph — moving it in step with the CLI
`--local` graph and the deployed backend parser.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix(pipelines): make node & pipeline-level run affordances always visible
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(pipelines): don't leave node Run disabled by stale form validity
The always-visible node Run button read `isValid` directly, but that flag is
only meaningful while PipelineRunForm is mounted to set it. On a same-path
re-resolve (the component is keyed on script.path) from an input-carrying
script to an input-less one, the form unmounts leaving `isValid=false`, which
wrongly kept the empty-args Run button disabled. Gate validity through a
`runValid` derived that is true whenever no form is rendered.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(pipelines): run with {} when node has no form, not stale args
The always-visible node Run button dispatched $state.snapshot(args)
unconditionally. `args` persists across a same-path re-resolve (component keyed
on script.path), so a script that once had inputs/partition args and is
re-resolved as input-less would run with the stale hidden args instead of {}.
Send {} whenever no form is rendered, matching the no-form run intent.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(cli): surface macro libraries in --local pipeline graph + make run --dry-run read-only
* fix(cli): resolve workspace-wide macro libraries in --local graph (shared libs outside the pipeline folder)
* fix(cli): macro-lib consumers + //-prefix parity in --local pipeline graph
Address Codex review P1s: (1) macro libraries that consume another library's
macros now produce lib->lib edges (any folder DuckDB script is a consumer, not
just // pipeline members) so an upstream provider node no longer disappears;
(2) parseMacroAnnotations accepts //, --, and # prefixes like the backend, so a
.duckdb.sql library headed with // macros is detected locally. Both edge
endpoints are forced into the node set. Verified byte-for-byte against deployed.
* fix(cli): exclude non-pipeline macro-consumer nodes from --local run selection
Address Codex P1: buildMacroEdges surfaces macro-consumer nodes (a DuckDB script
calling a macro but not marked // pipeline) for lineage display. Those have no
local file, so pipeline run --local must not treat them as manual roots — a
dry-run listed them and a real run failed resolving local content. Exclude any
--local graph node absent from localScripts (the previewable set) from starts and
selection, alongside the existing macro-library exclusion.
* fix(cli): reject display-only macro consumers in explicit --from (post-merge with #9945)
The mid-DAG --from feature (#9945, now on main) admits any autorun-able script
via validFromStarts/fromEligible, which was filtered only by macroLibPaths. A
non-// pipeline macro-consumer helper (a --local display node) therefore passed
--from eligibility and produced an empty plan. Filter fromEligible by the broader
notRunnablePaths too, and reject such a --from with a clear message instead of a
silent empty plan.
* chore(cli): remove NUL edge-key separator + refresh stale macro comments
Address Codex P2 nits: (1) the macro edge map packed (lib, consumer) into a
string with a literal NUL separator, which made localGraph.ts read as a binary
file to grep/rg — replace with a nested lib->consumer Map (no separator); (2)
comments claiming macro nodes/edges are 'deployed graph only' contradicted this
PR's local derivation — describe the code as it is.
* fix(cli): tag unused // pipeline + // macros libraries so --local run excludes them
Address Codex P1: the deployed builder sets 'macros' on any node whose path
provides macros (edge or not), so a // pipeline + // macros script with no
consumers is still recognized as definition-only. Local enrichment only tagged
edge providers, leaving an unused pipeline macro library as a bare runnable that
pipeline run --local would schedule as a manual root. Also tag any library whose
path is already a runnable; unused non-pipeline libraries stay suppressed.
* fix(pipelines): `// macros` takes precedence over `// pipeline` (a library is never a member)
A macro library is definition-only — its macros are injected into consumers and
running it is a no-op — so marking it `// pipeline` is meaningless and only
produced a confusing state (an unused pipeline macro library appearing as a
manual root). Make `// macros` win: parse_pipeline_annotations forces in_pipeline
false when macros is set. Mirrored in all three parsers that must agree — the Rust
canonical parser (drives deploy membership), the frontend TS parser (live graph),
and the CLI local graph (pinned wasm still reports in_pipeline, so precedence is
applied when skipping members). Shared parity fixture + unit tests on each side.
* docs(cli): trim narrative comment blocks to non-obvious constraints
Address Codex P2: duckdbMacros.ts opened with a ~19-line narrative block whose
parity rationale belongs in the PR description; reduce to the two real constraints
(keep in lockstep with duckdb_macros.rs; dynamic-SQL calls need // use). Per the
AGENTS.md comment policy.
* fix(cli): model macro libraries as pipeline members, matching the deployed graph
Reverts the parser-precedence approach (b398b69): the backend deliberately marks
EVERY macro library auto_kind='pipeline' (scripts.rs:1474, macro_lib_defs), so a
macro library IS a graph member — the // pipeline marker is redundant, not
authoritative. Precedence was a no-op on deploy while diverging the CLI/frontend.
Instead mirror reality in the CLI local graph: an in-folder // macros library is a
member node (in_pipeline=true, with signatures) whether used or not; its // use is
processed (it's a member) so a library that reaches another only via dynamic SQL
still gets the via_use lib->lib edge (fixes the missing-edge case); an out-of-folder
library referenced by an in-folder consumer is a non-member provider node. Macro
libraries stay excluded from runs (via macros) and from the previewable scripts set.
Verified byte-for-byte (incl. in_pipeline) against the deployed graph: unused
in-folder lib, lexical lib->lib chain, // use dynamic-SQL lib->lib, out-of-folder
shared lib.
* fix(pipelines): canonicalize S3 asset keys so SDK writes and DuckDB reads connect
The SDK object forms — TS `writeS3File({s3:"exports/x"})` and Python
`write_s3_file(S3Object(s3="exports/x"))` — resolve to the URI `s3:///exports/x`
(empty default storage), whose parsed asset path was `/exports/x` (leading
slash). DuckDB `read_csv('s3://exports/x')` and the `// on s3://exports/x`
trigger form yielded the bare `exports/x`. The same object thus produced two
asset identities, so a DuckDB consumer never connected to a TS/Python producer
in the pipeline graph.
`parse_asset_syntax` (shared by the native backend parsers and the wasm parser
that drives `frontend/src/lib/infer.ts` and the CLI `localGraph`) now strips a
single leading slash from S3 paths, so `s3:///key`, `s3://storage/key`, DuckDB
`s3://…`, and `// on` all canonicalize to one key. Both deploy-time inference
and editor/CLI inference agree, and the producer's write edge and the
consumer's read/trigger edge share a node.
Only one leading slash is stripped, so `s3:///` triple-slash default-storage
keys collapse to the bare key while Hive-partition keys
(`s3://bucket/y=2024/f.parquet`) and explicit-storage `s3://storage/key` paths
are untouched. Non-S3 asset kinds (res://, ducklake://, …) keep their paths
verbatim.
Note: existing deployed pipelines that recorded `/key` paths need a redeploy to
pick up the canonical `key`; the fix is forward-consistent for anything parsed
after this change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(pipelines): mark S3 asset-path normalization (item 6) resolved
The open-issues list still flagged the SDK-form leading-slash vs bare-URI
no-slash mismatch as "Still open", contradicting the fix in this PR. Mark it
resolved to match the updated Language-coverage prose.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs+test(pipelines): disclose S3 explicit-storage vs default-storage-nested-key aliasing
Collapsing to one canonical key means `s3://storage/key` (explicit storage) and
`s3:///storage/key` (default-storage nested key) now alias to the same node
`storage/key`, though they name different objects. Low-probability (needs a
storage config named to match a default-storage prefix) and inherent to a
best-effort lineage graph that doesn't split the first segment as a storage
name, but previously undisclosed. Document the tradeoff and pin the intended
aliasing with a test so it's intentional, not a latent surprise.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(pipelines): mirror S3 leading-slash strip in frontend live-preview parser
The pipeline graph live preview parses `// on` annotations client-side via the
hand-written `parsePipelineAnnotations.ts` (a TS mirror of the Rust annotation
scanner), NOT the wasm parser. Its `parseAssetSyntax` still returned the raw
suffix, so `// on s3:///exports/x` yielded `/exports/x` while the deploy-time
and wasm parsers now canonicalize to `exports/x`. `resolveGraph` synthesizes
trigger edges from that path, so the browser preview could still render
disconnected `/exports/x` and `exports/x` nodes for the exact triple-slash case
this PR fixes at deploy time.
Mirror the S3-only single-leading-slash strip in the TS parser and extend the
shared parity fixture corpus (run by both the Rust and TS parity suites) with
the triple-slash trigger case, so Rust/TS drift on this is now caught.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(pipelines): seed slashless S3 template asset paths to match canonical key
`autoOutputAsset` seeded new S3 template outputs with a leading slash
(`/pipelines/…`), which the old parser required to match `s3:///key` writes.
This PR made `parse_asset_syntax` strip that slash, so the seeded draft asset
(stored as `outputAssets`, used by `resolveGraph` for inactive-draft node
identity) no longer matched the body-inferred identity `pipelines/…` — the live
preview could render a duplicate `/pipelines/…` node and a phantom post-deploy
drift warning.
Seed the canonical slashless key instead, and switch the DuckDB body's S3 URIs
from `s3://${path}` to `s3:///${path}` so the generated runtime URI stays the
triple-slash default-storage form byte-for-byte (the SDK sites already build
`s3:///` + bare key). Add a pure-logic parity test asserting, for every
language and S3 output kind, that the seeded asset path is slashless and that
every S3 URI the generated body emits is triple-slash and canonicalizes back to
that seeded path.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(pipelines): canonicalize S3 keys in CLI + frontend bounded-cascade resolvers
Two more hand-written S3-URI sites returned the raw suffix, so `s3:///exports/x`
stayed `/exports/x` while native/wasm parsers now canonicalize to `exports/x`:
- `cli/src/commands/pipeline/localGraph.ts` — the no-wasm fallback `// on`
scanner (go/bash/ruby). A fallback consumer's `// on s3:///x` would not
connect to a wasm-inferred `x` producer in `wmill pipeline show/run --local`.
- `boundedCascade.ts` `assetUriToNodeId` (duplicated in the CLI and the frontend
AssetGraph engines, kept in sync) — `--to s3:///exports/x` / a cascade bound
token would not resolve against the canonical graph node `s3object:exports/x`.
`resolveToken` delegates here, so it is covered too.
Mirror the S3-only single-leading-slash strip in all three, and add `s3:///`
tests to the CLI local-graph fallback suite and both bounded-cascade suites
(explicit-storage and Hive-partition keys asserted untouched).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(pipelines): phrase S3 template test comment as a current invariant
Describe the slashless-seed requirement as the invariant it is, not as change
history, per the AGENTS.md "describe the code as it is" rule.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(pipelines): strip all leading slashes from S3 keys so trigger refs round-trip
`parse_asset_syntax` stripped only one leading slash, so `S3Object(s3="/x")` —
which resolves to the quad-slash URI `s3:////x` — parsed to path `/x`. But
`trigger_spec_to_row` rebuilds a stored trigger ref as `s3://<path>` =
`s3:///x`, which `parse_asset_trigger_ref` then parses back to `x`. The
producer recorded `/x` while its consumer trigger resolved to `x` → a broken
edge. The same asymmetry affects every `s3://`+path reconstruction site
(backend refs, frontend `assetUri`, page refs) whenever a path starts with `/`.
Strip ALL leading slashes so a canonical S3 path never starts with `/`; naive
`prefix + path` reconstruction then round-trips everywhere. Applied uniformly
across all six S3-URI sites (Rust `parse_asset_syntax`, the TS live-preview
parser, template `s3Key`, and the frontend+CLI `assetUriToNodeId` and CLI
fallback scanner). The pathological leading-slash key collapses to the bare key
— acceptable for a best-effort lineage graph that never split storage anyway.
Tests: a windmill-common round-trip test (parse → trigger_spec_to_row →
parse_asset_trigger_ref) over every URI form incl. the quad-slash case; a
`s3:////x` shared parity fixture (Rust + TS); and quad-slash assertions in the
Rust parser test and both bounded-cascade suites.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(pipelines): align S3 template parity helper with strip-all canonicalization
The template seed/body parity test's `canonicalS3Key` helper (and its comment)
still stripped a single leading slash, so it no longer mirrored the parser it
claims to pin. Strip all leading slashes to match `parse_asset_syntax` and the
frontend/CLI mirrors.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(pipelines): mid-DAG selective execution (dbt `model+`) for pipeline runs
Relax the root-only constraint on bounded-cascade starts so `--from` can name
any node in a pipeline — not just a schedule/manual root. A mid-DAG start runs
that node plus its transitive downstream and never re-runs upstream, giving
dbt's most common gesture (`dbt run --select model+`) a direct form:
wmill pipeline run f/orders --from fct_orders_daily
Previously this errored with "Starts must be schedule-triggered or manual
roots". The bounded-run engine already computed downstream/path-between sets
generically; only the eligibility gate was root-only.
- Shared engine (`boundedCascade.ts`, CLI + frontend mirror): add
`validFromStarts` — every autorun-able script (roots AND mid-DAG asset
subscribers / pure readers), excluding only event/input-only handlers
(kafka/mqtt/…/webhook/data_upload) that can't run with empty args.
- CLI: `--from` accepts any `validFromStarts` node; asset `--from` and
non-autorun handlers still rejected (the latter runnable via `--upload`). An
explicit mid-DAG start is protected from the barrier cut. Help text + regenerated
system_prompts describe the new surface.
- Frontend graph UI parity: any node with downstream now offers "Run + downstream…"
(was roots-only). With no end picked the bounded-run bar runs the full downstream
closure (`model+`); picking end(s) still bounds the path-between set.
- Unit tests for the new selection semantics in both engines.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(pipelines): address CI review — scheduled-root --from regression + pick-mode barrier parity
Codex review findings on #9945:
- P1: explicit `--from` rejected a scheduled root that also carries a secondary
non-autorun trigger (e.g. `// on schedule` + `// on data_upload`), even though
it stays a valid IMPLICIT start. `validFromStarts` excluded anything in
`nonAutorunTriggerScripts`; now it unions in `validStarts` (which lets the
schedule identity win over the secondary trigger), so a scheduled root is
`--from`-eligible in both CLI and the graph UI. Regression tests added in both
engines.
- P2: bounded-pick mode built `eligible` (pickable end bounds) from raw
`descendants`, so an event handler — or a node only reachable through one —
could be clicked as an end yet be silently dropped from the barrier-cut run.
`eligible` is now the barrier-cut closure, so those nodes are dimmed and
non-pickable. The highlighted `bounded` ring now also reflects the actual
(barrier-cut) run set, including the no-ends "Run + downstream" case.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(pipelines): frontend barrier set must exclude all valid roots, not just the picked start
Codex review follow-up: the frontend `boundReachable` barrier set only protected
the picked start (`id !== boundPickStart`), while the CLI protects every valid
root (`!starts.has(id)`). So a scheduled root that also carries an event trigger,
reached downstream from another start, was wrongly treated as a barrier — the UI
dimmed/skipped it and its downstream, diverging from the CLI run set.
Exclude `validStarts` from the barrier set too (a scheduled/manual root runs on
its own identity even with a secondary event trigger). Regression test asserts a
scheduled-event root and its downstream stay reachable from an upstream start,
and that the naive (start-only) barrier set would have dropped them.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(pipelines): frontend must exclude webhook/data_upload as mid-DAG autorun starts
Codex review follow-up: the frontend `validFromStarts` only excluded
`EVENT_TRIGGER_KINDS`, so a mid-DAG `webhook`/`data_upload` subscriber was added
by the new eligibility loop — the UI would offer "Run + downstream" and launch it
with empty args (no uploaded S3Object / webhook body). The CLI mirror already
excludes these input-only kinds.
Add a frontend `NON_AUTORUN_TRIGGER_KINDS` (event kinds + webhook + data_upload),
mirroring the CLI, and use it in both `validFromStarts` (exclude such mid-DAG
handlers from starts) and `nonAutorunTriggerScripts` (cut them as barriers).
When the marker is visible (editor overlay / draft) these are now handled
exactly as the CLI does; the deployed-graph blind spot (no webhook/data_upload
rows) remains the documented pre-existing `validStarts` limitation.
Regression test: a `data_upload`/`webhook` mid-DAG subscriber is not an eligible
start and is barrier-cut (with its exclusive downstream) when running from an
upstream root.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat(pipelines): partition run-arg picker + first-run setup signpost
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(pipelines): honor partition start= anchor in picker default and per-input upstream hint
Addresses CI review (Codex):
- P1: never seed/offer a pre-start bucket — the worker takes an explicit partition arg verbatim, so seeding today's bucket before start= would materialize early. defaultBucket now clamps to the start bucket and drops pre-start recent-missing chips; a hint explains the start anchor.
- P2: upstream-missing hint checks each partitioned ducklake input separately instead of unioning, so a fan-in where one input has the bucket no longer masks another that lacks it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(pipelines): reseed partition picker on header change and fail safe on bad tz/start
Addresses second CI review (Codex):
- P1: run form is now keyed on the parsed partition spec as well as the schema, so editing the // partitioned header (same schema) remounts the picker and reseeds/re-strips instead of keeping a stale bucket that re-bypasses the start anchor.
- P1: malformed metadata is fail-safe in parity with the backend — invalid tz= no longer throws in Intl (falls back to UTC for display), invalid start= (e.g. 2026-02-31) is rejected via round-trip check, and neither auto-seeds an explicit partition (which would bypass the worker's own tz/start validation). A warning hint points at the header.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(pipelines): pipeline-level run control, tables label, data-test rollback + fork badges
- Add always-visible "Run pipeline" header control (edit mode) that runs
every script in dependency order via the bounded-cascade engine, so a run
no longer requires hovering a node's play button.
- Header summary counts ducklake/datatable assets as "tables" (and s3object
as "files") instead of the raw kind, collapsing shared nouns.
- Surface a data-test outcome badge on guarded asset nodes: EE shows a
rolled-back (previous version left live) state, CE shows published-despite-
failure — driven by the producer's last run state and the edition.
- Make the fork data-environment marker a prominent labeled chip
(⑂ fork / ↗ parent) instead of a bare icon.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(pipelines): address CI review — scope Run pipeline to members, anchor guard badge, spin loader
- Run pipeline now filters to `in_pipeline` script runnables, so it never
launches dependency-only endpoints the graph shows for context (macro
libraries, custom data-test scripts, out-of-folder producers).
- Data-test guard badge only attaches to the producer's declared
`// materialize` target, so a multi-output producer no longer badges its
other ducklake writes.
- Spin the Loader2 icon in the "Run pipeline" button while a run is in
progress (startIcon classes), matching every other loading affordance.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(pipelines): data-test badge copy speaks to write policy, not failure cause
producerFailed is a generic job-failure signal, so the failed-state tooltip
no longer claims the run "failed its data tests" (it could be a runtime/worker
error). It now states the edition's behavior on any failed materialize: EE
rolls back (previous version left live), CE may leave a failing write live.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(pipelines): Run pipeline keeps independent branches running after a failure
runSelection used a single global fail-fast flag, so once any node failed it
refused to schedule *any* newly-ready node — a failure in one branch could
strand an unrelated healthy branch as 'skipped' depending on job timing. Now a
failure poisons only its transitive descendants; independent branches finish.
Add regression tests: independent-branch-survives-failure and join-node-skipped
-when-one-upstream-fails.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
`ATTACH 'datatable://main'` (or any datatable schema/executor path) failed
with a bare "datatable main not found", giving the user no way forward — the
datatable substrate has no auto-provisioning like a DuckLake catalog, so the
fix is always to create one in workspace settings, but nothing said so.
`get_datatable_resource_from_db_unchecked` now returns a NotFound error that
lists the workspace's configured data tables (to catch typos) and points at
the "Data tables" settings tab, noting `main` is the default name used by
`datatable://main`. The message bubbles up wherever the resolver is called
(pipeline ATTACH, schema fetch, postgres executor, agent HTTP endpoint).
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(pipelines): link SCD2 <dim>_current view to its producer across all graph surfaces
An SCD2 producer (`// materialize … history`) creates the base table AND a
`<dim>_current` view at runtime. The deploy path already registered both writes,
but the CLI `--local` graph and the frontend live-editor graph only emitted the
base write, so a consumer reading only `<dim>_current` orphaned there. Centralize
the companion derivation in `MaterializeSpec::write_targets` /
`scd2_current_target` (+ TS `scd2CurrentTargetPath` mirror), emit the `_current`
write in every surface, and mark the companion node `derived_from` the base so the
canvas renders it as a derived "current view" instead of an unrelated table.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(pipelines): keep scd2 _current write edge when editing a saved producer
Addresses Codex CI review (P1): opening a deployed scd2 materialize producer for
editing dropped its persisted `<dim>_current` write edge. `liveRefKeys` (the set
of asset keys a saved-script edit preserves against stale-filtering) only added
the base materialize target, so the companion `_current` write was judged stale
and filtered — orphaning consumers of only the view mid-edit. Add
`scd2CurrentTargetPath(m)` to `liveRefKeys` too; covered by a new saved-edit 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>
* fix(pipelines): order data_test relationships refs before the tested script in a cascade
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(pipelines): key custom-test reads by (usage_kind, path) to avoid same-path flow collisions
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Custom `// data_test <path>` scripts must be a single SELECT reading the
freshly-materialized target via the internal `_wm_target.<table>` alias —
neither was documented or scaffolded. Make the codegen errors name the exact
violation (multi-statement, non-SELECT, wrong alias, empty) and append a
copyable `SELECT * FROM _wm_target.<table> WHERE <condition>` example. Add a
DuckDB-only 'Data test' pipeline output kind that scaffolds that starter body.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two correctness/validation improvements to managed materialization:
1. A keyed `merge` (`key=<col>`) is delete-by-key + insert-all and does NOT
deduplicate its source, so two incoming rows sharing a key both landed
under that key — silently breaking the one-row-per-key contract. Codegen
now emits an in-transaction guard (same `error(...)` shape as the schema
-drift guard) that fails the run when the SELECT returns more than one row
for a non-NULL key, naming the key. Authors deduplicate in the SELECT or
switch to `append`. NULL keys are exempt, matching the delete's `IN (...)`
scope.
2. The two SCD2 misconfigurations that were only caught at run time — `history`
without `key=`, and `history` + `// partitioned` — now fail fast at deploy
via a shared `MaterializeSpec::validate`, called from `create_script_internal`.
The DuckDB executor keeps the same check as a safety net for preview/test
runs that never deploy (shared message, no drift).
Adds unit tests for the merge guard codegen and for `validate` (all four
cases), and updates docs/ducklake-materialization.md and docs/pipelines-vs-dbt.md.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(object-storage): remove 20-file bucket-browser cap in CE
The Community Edition build rejected the object-storage `list_stored_files`
endpoint with an error once a workspace bucket held more than 20 objects,
making the bucket browser unusable on larger buckets. The listing already
collects up to `max_keys` objects, so the hard cap was purely a gate.
Drops the CE listing cap (in the EE-symlinked `job_helpers_ee.rs`, tracked
in the companion windmill-ee-private PR) and removes the now-inaccurate
sentence from the workspace object-storage settings banner. The 10 GiB
total-storage write quota remains as the intentional CE limit.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 4bd7b73dcef1b77dc2866bc58e0c86962559495c
This commit updates the EE repository reference after PR #648 was merged in windmill-ee-private.
Previous ee-repo-ref: aa14d0724216030948c2f575bcc19c0e6e0476a7
New ee-repo-ref: 4bd7b73dcef1b77dc2866bc58e0c86962559495c
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>
* fix(duckdb): render FFI errors with real newlines and no stray quoting
The DuckDB FFI returns errors as ERROR <json-encoded-message>, so the
executor was surfacing the serde_json-escaped form (wrapping quotes,
literal \\n). Multi-line errors like the write-audit-publish data-test
breakdown were unreadable. Decode the JSON string back to the raw message
at both FFI error sites.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: tighten decode_ffi_error comment to the invariant
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(pipelines): clarify activity-window axis label + select failed node on cascade failure
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(pipelines): clear active draft so failed-node focus is not masked
PipelineGraphEditor gives an open draft (activeDraftPath) priority over
selection via openScriptPath, so the cascade-failure focus set only selection
and stayed masked while a draft pane was open.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(pipelines): on_schema_change write guardrails + data_test deploy validation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore: update ee-repo-ref to fa7ac11c1e0ab39e84a0c18973ba427a240933ca
This commit updates the EE repository reference after PR #647 was merged in windmill-ee-private.
Previous ee-repo-ref: bd23b2a904cb2e6554c7ff209ff8adb9d91775d1
New ee-repo-ref: fa7ac11c1e0ab39e84a0c18973ba427a240933ca
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* fix(cli): publish all windmill-parser-wasm-* deps so local pipeline graph keeps write edges
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* style: trim explanatory comment blocks to core constraints
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(pipelines): fork-scoped ducklake namespaces with read-defer to parent
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(pipelines): fork graph indicator + fork ducklake namespace cleanup endpoint
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(pipelines): fork_views-keyed view transition, fork lineage clone, design doc
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(pipelines): review hardening - fork DATA_PATH last-wins, registry cache TTL, defer tests
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(pipelines): per-lake isolated/shared choice at fork creation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(pipelines): chain-aware defer discovery + per-location fork namespace registry
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(pipelines): lake-scoped fork schemas, catalog identity in registry, chain-aware graph chips
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(pipelines): cleanup deletes fork data from the registered storage identity
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(pipelines): collapse fork data-path segment to one component (slash-safe ids)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(pipelines): per-catalog ancestor checks, ancestor extra_args passthrough, test compile fix
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(pipelines): invalidate fork ancestor-chain cache on lineage mutations
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(pipelines): sweep descendant ancestor-chain caches on delete/reparent
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(pipelines): run fork ducklake cleanup inline in delete_workspace
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(pipelines): resolve fork cleanup credentials pre-commit, destroy post-commit
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(pipelines): shared dev-workspace authz gate for namespace drop, invalidatable registration cache, segment-boundary delete filter
- extract require_prod_admin_for_dev_workspace, used by both delete_workspace
and drop_forked_ducklake_namespaces so the gates cannot drift
- key FORK_DUCKLAKE_REGISTERED per workspace and invalidate it in
cleanup_fork_ducklake_namespaces so a same-id fork recreated within the TTL
re-registers its namespaces
- filter listed object locations to the segment boundary before deletion
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(pipelines): keep orphaned wm-fork-* workspaces ducklake-isolated
parent_workspace_id is ON DELETE SET NULL, so a fork can outlive its
parent with an empty ancestor chain while its cloned config still points
at the shared lake. Key the isolation gate on the wm-fork- prefix as well
as the chain (mirroring workspace_is_fork): orphaned forks get the write
redirect, registration and cleanup with zero ancestors (no defer), and
keep their 'fork' graph chips.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(pipelines): attach orphaned wm-fork-* ancestors at their fork namespace
Chain position alone classified the last ancestor as a root, but an
orphaned wm-fork-* ancestor (its own parent deleted, SET NULL) ends the
chain the same way while its data lives in its fork namespace — its
descendants' defer views bound the dead root's lake instead. Key the
root-vs-fork decision on the wm-fork- prefix too, matching the
resolution gate.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(pipelines): never inherit shared lake opt-out; durable cleanup ledger for failed fork deletions
- fork creation strips cloned fork_behavior stamps before applying the
request's shared_ducklakes list: sharing is a per-creation choice, a
fork of a shared fork defaults back to isolated
- fork_ducklake_namespace loses its ON DELETE CASCADE FK: rows are the
durable cleanup ledger and outlive the workspace when physical cleanup
fails post-commit; fork creation retries leftover rows for the reused
id and refuses to create while a metadata schema still cannot be
dropped (data-file leftovers alone are inert once the schema is gone
and are swept by the next successful same-prefix cleanup)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(pipelines): make orphaned-namespace cleanup retries independent of deleted fork resources
- ledger rows gain a schema_dropped phase flag: set when the schema drop
succeeded but data cleanup failed, so later retries skip the schema
phase and need no catalog credentials at all; registration resets it
on re-attach (ON CONFLICT DO UPDATE) since attaching recreates the
schema
- retry-path $res: resolution falls back to the workspace being forked
(the deleted fork's resources were clones of a parent's); live paths
(delete_workspace prepare, drop endpoint) pass no fallback
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(pipelines): fork tables from failed-after-commit runs stay fork-owned in defer and graph
A failed materialization must not disguise a physically existing fork
table as deferred: CREATE VIEW IF NOT EXISTS silently yields to the
table, so reads hit fork data while the graph claims parent defer.
- record_mat upsert preserves the last committed snapshot_id on failure
- defer discovery and graph chips treat fork rows with a committed
snapshot as fork-owned even when status is failed
- inspect_fork_catalog also lists live fork tables (same round trip) and
the defer list is filtered against them — covers rows recorded before
this fix and tables created by raw SQL
- drop stale FK-cascade wording in the design doc and sidebar comment
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(pipelines): fork-mode ducklake settings — per-lake isolated/shared chips + banner, fork_behavior round-trip
The workspace-settings ducklake editor had no fork awareness: no
reminder of each lake's isolated/shared choice and no warning about
what edits mean in a fork. It also rebuilt each lake explicitly on
save, silently dropping fork_behavior — any settings save in a shared
fork flipped the lake back to isolated.
- fork detection mirrors the backend gate (parent link or wm-fork- prefix)
- info banner explaining isolated vs shared semantics in a fork
- per-lake chip (emerald 'isolated' / amber 'shared with parent') with
tooltips, matching the pipeline graph chip colors
- fork_behavior added to DucklakeSettingsType and preserved through
convertDucklakeSettingsToBackend
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(pipelines): capture violating-row samples for data tests
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(pipelines): byte-accurate sample cap and leaf-level payload sanitize
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore: bump ee-repo-ref to WAP guard probe adaptation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: WAP failures are counts-only — samples exist only on commit-then-test
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: qualify where sample row data appears — job result and failed-job log line
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: error handlers receive the full result incl. samples, like any failed job
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore: update ee-repo-ref to 80d309edebb899e36a3bdcdf4ea73c4db070534d
This commit updates the EE repository reference after PR #646 was merged in windmill-ee-private.
Previous ee-repo-ref: 16e916bf11f26381920560b55771fce693e668c6
New ee-repo-ref: 80d309edebb899e36a3bdcdf4ea73c4db070534d
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* feat(pipelines): ingestion (EL) templates + docs
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(pipelines): review nits — draft collision guard, template-mode selection reset, invariant test
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(pipelines): lead the insert menu with ingestion templates
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(pipelines): ingestion story as docs-only — drop editor template UI
The insert-menu template section mixed two selection grammars in one popover and confused more than it helped. The three E2E-verified example pipelines now live verbatim in docs/pipeline-ingestion.md; the Python bare-string S3 key fix in pipelineTemplates.ts stays.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(sdk): bare string S3 keys in py/ts clients + asset parsers
A plain string passed where an S3Object is expected is now a bare key in the default storage — previously the py client silently degraded it to s3="" (auto-generated key) and both asset parsers canonicalized it without the leading slash, splitting lineage. parseS3Object moves to s3Types.ts so it is unit-testable without the generated services. The pipeline template fix from the earlier commit is superseded (bare strings are the supported spelling again); docs examples flipped to bare keys.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(sdk): enforce s3:// URIs for string S3Object params
Bare strings now raise/throw with a hint pointing at the s3:///<key> spelling instead of being treated as keys (previous commit) or silently degrading to an empty key (original behavior). One string spelling everywhere: SDK calls, // on annotations, and DuckDB SQL all use s3:///<key>. TS regains the s3://-template-literal type; the asset parsers record no asset for a bare string (the call can only error); templates emit the URI form.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(pipelines): move ingestion (EL) guide to windmilldocs, keep design constraints
User-facing how-to (engine choice, cursor recipes, schema drift, worked examples) moves to windmilldocs core_concepts/63_pipelines (windmilldocs#1462); the repo keeps only the design constraints future feature work must not break, as a section of ducklake-materialization.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore: regenerate system prompts after parse_s3_object docstring change
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(sdk): reject empty-key s3 URIs; align asset parsers with the runtime rule
Addresses CI review: s3:/// and s3://bucket/ now raise (an empty key would fall back to the auto-generated-key path the strict contract exists to prevent); the asset parsers' string branch applies the same valid-URI-with-non-empty-key rule so no R/W edge is recorded for a call that can only error (the generic URI-literal scan still records ambiguous access-None assets, by design); comments rephrased as current constraints per AGENTS.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(ducklake): scheduled lake maintenance (snapshot expiry, compaction, orphan cleanup)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(ducklake): review fixes — starts_with not LIKE, CE license-lapse escape
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(ducklake): auth-contract docs + _unchecked rename per codex review
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(ducklake): move maintenance payload construction into EE module
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(ducklake): fall through to script resolution for non-managed reserved-prefix schedules
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(ducklake): document accepted pre-existing-schedule limitation on the reserved prefix
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(ducklake): CE save-off clears the managed schedule row and queued occurrence
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore: update ee-repo-ref to 2fab310d4f50ed7c34857d69c9b854f4491bf217
This commit updates the EE repository reference after PR #645 was merged in windmill-ee-private.
Previous ee-repo-ref: fff1fd830a36beba732486f05941ec243cf6b640
New ee-repo-ref: 2fab310d4f50ed7c34857d69c9b854f4491bf217
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* feat(pipeline): write-audit-publish for materialization data tests (EE)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: EE worktree E0583 troubleshooting + duckdb feature check row
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: clarify EE symlink example (absolute target, EE repo layout)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(pipeline): move bootstrap DDL inside guarded WAP transaction
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(pipeline): move WAP guard SQL builder into EE, OSS keeps placement only
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore: bump ee-repo-ref to EE branch rebased on EE main
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* style: reword test comment as current invariant per AGENTS.md
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore: bump ee-repo-ref (EE module doc update)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(pipeline): OSS emits typed materialize plan, EE owns WAP transform
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test: make rewrite assertion build-aware; refresh oss module doc
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore: update ee-repo-ref to 7be0bad1a6d6b5c3a107c0a2cd4bf003c36ec34c
This commit updates the EE repository reference after PR #644 was merged in windmill-ee-private.
Previous ee-repo-ref: 63cabae75329429f647e01083936d70f8197dc9e
New ee-repo-ref: 7be0bad1a6d6b5c3a107c0a2cd4bf003c36ec34c
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* feat(ai-agent): support reasoning effort in AI agent workflow steps
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai-agent): round-trip native Anthropic thinking blocks and fix DeepSeek/Mistral reasoning
Address review: native Anthropic now captures the signed thinking block during streaming and replays it before tool_use across iterations (prevents a 400 on multi-turn tool use). DeepSeek 'off' sends thinking:{type:disabled} instead of the rejected reasoning_effort:none, and Mistral drops temperature when reasoning is on.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(ai-agent): move reasoning effort into the provider/model selector
Store reasoning_effort on ProviderConfig (next to the model) instead of a separate flow arg, and render the selector inside AIProviderPicker under the model dropdown. Add an explicit 'off' option on models that disable reasoning by omission (e.g. Claude), so reasoning can always be turned off from the UI.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(ai-agent): use DropdownV2 for reasoning effort, matching copilot chat
Replace the Select combobox with the same DropdownV2 action-menu the copilot chat reasoning selector uses. Each option carries an action instead of a bound value, so click selection is unambiguous and there is no typeahead/sentinel-value mismatch on the off/default entries.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(ai-agent): regenerate system prompts for ProviderConfig.reasoning_effort
Refresh system_prompts/auto-generated and cli skills.gen after adding reasoning_effort to the OpenFlow ProviderConfig schema (check-freshness).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai-agent): clear stale reasoning effort on model change; dedup bedrock reasoning folding
Address cubic review: (P1) the reasoning picker now clears the stored effort when the newly selected model doesn't accept it (e.g. carrying 'xhigh' from Opus onto a model that tops out at 'high'), not only when the model can't reason at all. (P3) the proxy's accumulate_reasoning_delta now delegates to the shared bedrock_stream_event_to_reasoning_delta so worker and proxy folding can't drift.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(ai-agent): stream reasoning summary and show a thinking affordance in flow chat
Add StreamingEvent::ReasoningTokenDelta, emitted from every worker reasoning path (Anthropic native thinking deltas, Bedrock, Gemini thought parts, OpenAI-compatible reasoning_content, OpenAI Responses reasoning_summary_text with summary:auto). The flow chat parses it and renders a collapsible 'Thinking' affordance on the assistant message (thinking tokens bill regardless of display, so surfacing the summary is billing-neutral).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai-agent): persist streamed reasoning onto the finished chat message
Reasoning isn't stored server-side, so the completion re-poll (which swaps temp messages for the persisted ones) was dropping the streamed thinking summary. Carry it onto the final assistant message so the 'Thought process' affordance survives the run.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(ai-agent): match flow-chat thinking box to the copilot chat reasoning UI
Replace the plain <details> thinking affordance with the same bordered, collapsible reasoning box the copilot chat uses (chevron + Brain/spinner + 'Thinking' header, markdown body, expand-while-streaming/collapse-on-answer).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai-agent): attribute streamed reasoning per turn by content; drop duplicated comment
Address review: the completion-poll carry-over now matches each temp assistant turn's thinking summary to its persisted message by content, so a multi-turn response (reasoning -> tool call -> final answer) no longer misattributes an earlier turn's thinking to the final answer or drops intermediate turns. Also removes a leftover duplicated comment block above the AIReasoningEffortPicker effect.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai-agent): address review round 2 (carry-over edges, off-token validity, aria, test)
cubic round 2: (1) reasoning carry-over now consumes temp turns in order verifying content, so identical/empty-content multi-turn responses attribute thinking correctly and reasoning-only turns aren't dropped; (2) the picker's stale-value check only accepts the off token when the model can actually disable reasoning; (3) add aria-expanded to the Thinking toggle; (4) add a test for the failed tool_result path.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai-agent): add bottom margin below the flow-chat thinking box
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai-agent): don't request OpenAI reasoning summary, matching the copilot chat
OpenAI gates reasoning summaries behind org verification, so requesting summary: auto would 400 for unverified orgs. The copilot chat requests effort only and never asks for a summary; align the worker with it (reasoning: { effort }) and drop the now-unreachable summary-delta parsing. OpenAI reasoning no longer streams a summary in flow chat (consistent with the copilot); Anthropic/Bedrock/Gemini/DeepSeek reasoning display is unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai-agent): scope reasoning carry-over to newly persisted messages
cubic round 3: matching by content over the full history could attach a new turn's reasoning to an older message with identical text. Restrict eligible targets to the messages just fetched for this response (via afterSeq), so historical turns are never touched.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai-agent): carry reasoning newest-first instead of gating on the final poll
cubic round 4: gating the carry-over on the final poll's filteredResponse dropped reasoning for messages already fetched by an earlier streaming poll (their id is excluded by afterSeq). Walk persisted newest-first and consume the newest matching pending summary, stopping once summaries run out. This response's turns are always at the end, so they claim their own reasoning (P1) before older history is reached (P2), regardless of which poll persisted them.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(ai-agent): drop flow-chat reasoning display, keep backend + effort picker
The chat-side thinking box relied on non-deterministically matching streamed (ephemeral) reasoning back onto persisted messages, which kept spawning edge cases. Remove the flow-chat display entirely (ChatMessage box, FlowChatManager carry-over/threading, parseStreamDeltas reasoning) and keep the sound backend: per-provider reasoning-effort requests, thinking-block round-trips for tool calls, and ReasoningTokenDelta streaming. A display can be built on top later, deterministically (e.g. once the stream carries the persisted message id).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai-agent): include reasoning_effort in default-config compare; document reasoning_token_delta
Codex/Pi nits: isSameAsStoredConfig now compares reasoning_effort so the 'use as personal default' toggle reflects effort-only changes; openflow streaming-events doc lists the reasoning_token_delta event (regenerated auto prompts).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(pipelines): passive asset freshness tracking on the graph
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore(pipelines): drop dead freshness-enforcement stub, document query ordering
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(pipelines): freshness watchdog (EE) — auto re-run stale producers
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(pipelines): watchdog review fixes — archived workspaces, badge kind parity, scan index
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(pipelines): CI review — no singlestepflow in freshness, +N parity, completion-time fallback
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(pipelines): CI review — history completedAt, freshness/asset trigger UI metadata
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore: update ee-repo-ref to 6f5fe0f7f56696fbef5a8349da38496c32e71666
This commit updates the EE repository reference after PR #643 was merged in windmill-ee-private.
Previous ee-repo-ref: 1f13380354bf591ae25a2c20d36917534bcc5459
New ee-repo-ref: 6f5fe0f7f56696fbef5a8349da38496c32e71666
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
The CLI docs generator anchored on the first `new Command()` in a file and
never resolved locally-defined command groups passed as
`.command("name", localCmd)`. For datatable this flattened the nested
`migrate` group: it emitted `datatable new/up/down` plus a bare
`datatable migrate`, and mislabeled the datatable command with the migrate
group's description. jobs was broken the same way (its description was pull's,
and pull/push rendered empty).
Anchor block extraction on the `export default`ed command, recurse into
locally-defined `const x = new Command()` groups mounted as subcommands, and
render nested sub-subcommands. Regenerated docs now show
`datatable migrate new/up/down` and `jobs pull/push` with their real
options.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(pipelines): record upstream snapshot ids on cascade-dispatched jobs
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor: batch upstream-snapshot lookup and memoize per subscriber
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
edit_datatable_config only validated rename segments, not the actual
settings.datatables keys, so a data table could be saved directly under a name
like '..' or one containing '/'. Since new tables default to
migrations_enabled = true, generate_initial_datatable_migration would then
insert a migration row and the sync export would build
migrations/datatable/<name>/... paths from that name, producing malformed or
directory-escaping export paths.
Validate every persisted data table name in edit_datatable_config (alongside
the existing rename checks) and add validate_datatable_path_segment to
generate_initial_datatable_migration for defense in depth.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>