* fix: stop the AI chat destroying secret variables on edit
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: clear stale staged secret values and state the draft-staging rule
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs: condense the pending-secret invariant to its field
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: refuse empty and oauth-managed secret values, keep drawer-staged ones in the draft
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: resolve a variable deploy's secret from one draft snapshot
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* refactor: make the variable draft the single source of a staged secret
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs: drop stale in-memory secret invariants from comments and the eval
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: stop null account/expires_at leaking into variable drafts and diffs
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: report when a variable deploy leaves the secret value unchanged
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs: scope the variable-value readability claims to the chat
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs: correct the secret-draft invariant in the diff masking comment
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs: record why a non-secret value is resent on a partial update
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: stop "Load secret value" discarding a staged secret
The audit-logged load writes the deployed secret into the draft row the
variable drawer shares with the AI chat, so offering it while that row
already stages a value silently replaces it — and the deploy that follows
carries the old value with no sign the staged one was lost.
The gate that hid the action already existed but keyed on
`isEncryptedDraftValue`, which only holds once a draft has round-tripped
through the server. A value staged in the same tab is still plaintext, so
it slipped through. Key on "anything staged" instead; clearing stays
explicit via Reset.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: extend the variable draft's empty-value sentinel past secrets
Two gaps in the chat's variable write path, both from treating "the draft
cannot carry this value" as meaning only "the value is secret".
`variableToDraftState` drops the value of an OAuth-managed variable so a
refreshed live token is never pinned into a draft, leaving '' behind. The
deploy body resent that '' verbatim for a non-secret one, wiping the token
the refresh flow owns. The sentinel now covers every value the draft is not
allowed to hold, which also removes the divergence from
`VariableEditor.save` and the shared deployer.
Making a variable secret when it holds no value produced a secret draft
staging '', a deploy body with no `value`, and the backend's "cannot change
is_secret without updating value too" — the sibling create path already
answers that case with guidance, so answer it here too.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: gate the Secret toggle's secret load on the staged value too
The toggle calls `onLoadSecret` on every change so an is_secret flip has a
value to send, but that load overwrites the shared draft row — the same
discard the button gate just closed, reached by a different control.
It now loads only when the row stages nothing, which is exactly when the
flip needs a value fetched. With a value already staged there is one to
send, and it is the one the user or the chat put there.
Blocking the load costs the side effect that used to mask a worse bug: for
a deployed variable, the load replaced an `$encrypted:` marker with real
plaintext before save. Without it, un-securing a marker would store the
marker string as the value, since the deploy endpoints only decrypt it while
is_secret stays true. So the toggle is disabled outright while a marker is
staged — Reset first. That closes the marker case for draft-only variables
as well, where no load could ever have masked it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
create_schedule opened the RLS transaction (user_db.begin) first, then ran
reads that deliberately use the non-RLS `db` pool — fork-ness and
permissioned_as/email resolution — while holding it. Acquiring a second pooled
connection while the tx holds one self-deadlocks on a single-connection pool
(embedded Postgres, PgBouncer statement mode, any max_connections=1 setup): the
read blocks on the sqlx acquire timeout, then errors.
Move those reads (and the ScheduleType::from_str validation) above
user_db.begin(). They don't depend on the tx and bypass RLS by design, so the
result is semantically identical; the RLS transaction is simply opened later and
held for less time. Same class of fix as #9970 (migration bootstrap on the
migrator's held connection).
Note: sibling paths keep the same latent pattern on branches this change does
not touch (push_scheduled_job reads the pool under the tx for flow schedules;
edit_schedule/set_enabled for cross-user permissioned_as) — a possible follow-up.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* test: wait for the app dependency job before pulling in repro_diffname
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* test: reuse waitForDeploymentJobs and assert pulled lock files
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* test: condense the dependency-job wait comment
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat: expose every runs filter on the open_page chat tool
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: reject runs filters the page would silently ignore
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: normalize runs list filters and refuse combinations the page drops
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: validate the full folder-name contract and pin evals to one call
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: refuse queue statuses the concurrency view cannot filter on
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: avoid content shift on home page load and in the script editor logs pane
The tutorial banner rendered by default and was removed once an API round-trip
resolved that it should not show, jumping everything below it up by 58px on
every home page load. It now caches the last resolved state in localStorage and
paints that first, so the first frame already matches what the sync concludes; a
device with nothing cached stays hidden until the sync answers.
The logs header spinner was an unsized lucide icon (24px) where the settled
state renders a 12px Timer, so the row grew 7px while a job was queued and
shrank back when it started, shoving the log body down and up again.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: keep the tutorial banner hidden when dismissed mid-sync
The banner is interactive while the initial tutorial-progress request is still
in flight, so a dismiss or a skip can land before the sync resolves. The
continuation then overwrote the user's choice and brought the banner back.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: pin the result placeholder row height across the spinner swap
Sizing the spinner to the font size still left it 6px short of the text-sm line
box it replaces, so the row contracted instead of growing. Pin the height on the
container so it holds in both states and tracks the root font size.
Also assign state before persisting it, and collapse the duplicated rationale
above the banner cache.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: stop the test panel splitpanes resting one header too tall
The panes carried `!max-h-[calc(100%-{...}px)]`, but the arbitrary value is
built by string interpolation so Tailwind never emitted a rule for it: the
class was inert and the computed max-height was `none`. The panes then took
their 100% height, ignoring the header row above them, and overflowed the
column by exactly the header. Flex only applied the shrink transiently, so a
reflow during a run snapped the whole logs & result region up ~12px and back.
min-h-0 lets flex size the panes to the space that is actually left, which is
what the clamp was reaching for and is correct for the debug and bottom layouts
too, without their hardcoded 83/43/0 pixel guesses.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: bound postgres result collection so it cannot OOM the worker
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: render the sql result limit exactly so the error can be set verbatim
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs: point the fraction rationale at the renderer that still emits them
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* perf: stop re-parsing every collected row to rebuild it as a RawValue
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* style: drop a dangling doc line and an unrelated rustfmt reflow
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: bound duckdb result collection so an oversized result cannot OOM the worker
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: keep the duckdb cap a worker-survival limit rather than a cloud product one
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: refuse an oversized blob before it expands to one json value per byte
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: share one expansion budget across a row's values, nested ones included
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: bound the row's own serialization so escaping cannot outgrow the budget
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: charge a json column before parsing it into a value tree
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs: trim the json budget rationale and name what the budget does not cover
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: gate the sql result size limit on the duckdb feature
Its only consumer is the duckdb executor, so the minimal build compiled it
as dead code and failed under -D warnings.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat: bound how much disk a single duckdb job can spill
* fix: name the env var and correct duckdb's unreachable spill-cap advice
* fix: do not blame an unset env var for duckdb's default spill cap
* style: keep the duckdb spill-cap invariant comments within four lines
* docs: size the duckdb spill cap against the disk cloud pods actually use
* fix: tell MCP clients which tool parameters may be omitted
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* refactor: make the mcp property-key rename testable and shorten the hint
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: keep the mcp omission hint from calling flow inputs optional
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: skip the mcp omission hint on a parameterless tool
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs: design for where the npm proxy keeps cached registry content
* feat(npm-proxy): keep package files on disk and in the object store
* fix(npm-proxy): degrade when the cache is unwritable, stream and bound it
* fix(npm-proxy): keep the happy path off the heap and isolate pull scratch
* fix(npm-proxy): bound the upload, verify pulled trees, keep oversized manifests
* fix(npm-proxy): protect live scratch, bound uploads by parts, refuse traversals
* fix: let the blocking unpack own the scratch it writes into
* fix: replace a cache directory that is not a package instead of deferring to it
* fix: evict by moving a package off the live path, not by deleting it in place
* fix: leave a package the sweep cannot move rather than deleting it in place
* fix: take one registry snapshot through a cache miss
* fix: stamp a pulled package as used so the sweep does not evict it first
* fix: explain duckdb failures caused by job isolation
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: apply the isolation policy to the schema-sync pre-pass
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* chore: bump ee ref for the out-of-memory hint wording
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: bump the bundled DuckDB engine to 1.5.5
The 1.5.5 duckdb crate no longer hands back a 96-bit `rust_decimal`, so a
DECIMAL wider than that renders instead of panicking inside an `extern "C"`
frame — which, being unable to unwind, aborted the whole worker process and
left the job running as a zombie. `SELECT
'1234567890123456789012345678.9012345678'::DECIMAL(38, 10)` was enough.
Adapting to the crate's API: `Value` is now `#[non_exhaustive]` and gained
`UHugeInt` and `Geometry`, and `rust_decimal` became an optional feature that
the `decimal`/`numeric` argument path still needs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: address review findings on the duckdb bump
Run the FFI crate's own tests in CI: it is excluded from the workspace, so the
`cargo test --all` in backend-test never reached them and the new guard against
the worker-aborting DECIMAL would not have run. build_dev.sh now honors a
caller-pinned CARGO_TARGET_DIR so the test build reuses that compile instead of
building the bundled engine a second time.
Also pin UHUGEINT rendering, and correct the rust_decimal rationale —
`Decimal::new` is public without the feature, so the reason is that the feature
reproduces the exact binding the crate used to derive, not that nothing else can.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: address review nits on the duckdb bump
Name the unsupported DuckDB type rather than dumping the value, which may be
arbitrarily large or hold data that does not belong in an error message, and
say which column it came from.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* chore: pin the ee ref to the narrowed duckdb extension allowlist
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat: keep duckdb spilling behind the local-filesystem fence
* chore: repin the duckdb fork after adding the reset-test exclusion
* docs: stop claiming the duckdb patch has been filed upstream
* docs: point the backend duckdb bullet at the fork's rationale
* fix: place lock_temp_directory so no existing struct member moves
* fix: skip the extension-load guard when the repo is unreachable
* refactor: trim the fork comments and fail the extension guard in CI
* chore: repin the duckdb fork onto upstream duckdb-rs main
* fix: keep the engine patch applying on a CRLF checkout
* docs: link the upstream issue tracking the underlying problem
* chore: repin the duckdb fork onto the patch as filed upstream
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 88568d11162ffa11723e7955e613224bab4f0568
This commit updates the EE repository reference after PR #720 was merged in windmill-ee-private.
Previous ee-repo-ref: 22f075c1164d9dd5a3ba92d682905aabd071d273
New ee-repo-ref: 88568d11162ffa11723e7955e613224bab4f0568
Automated by sync-ee-ref workflow.
* chore: repin the duckdb fork onto the cmake/fmt build fix
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* test: pin the immutability half of lock_temp_directory
The spill test proves the exemption works; nothing proved the lock that makes
it sound. A rebase could drop the refusals and leave every other tripwire green.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* fix(cli): route fileset children to their parent resource on sync push
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(cli): scope fileset pointer validation to sync pushes
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(cli): error on script push of file/fileset resource content files
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(cli): enforce server-canonical fileset pointers and fail fast before apply
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(cli): resolve ws-specific fileset metadata and validate pointers before dry-run
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(cli): prefer workspace-specific fileset metadata over base file
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(cli): make fileset metadata lookup assertions platform-separator safe
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(cli): stop dropping fileset children whose names look like typed metadata
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(cli): exempt fileset children from the current-workspace classifier too
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(frontend): declutter the workspace picker and workspace creation
* fix(frontend): fail open when the auto-invite domain check errors
* fix(frontend): fail closed when the auto-invite domain check errors
* feat(frontend): present prod and dev as sibling choices on the workspace card
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat(ata): prefer the npm proxy when the instance configures a registry
* fix(npm-proxy): cap tarball extraction and stop pinning a failed config probe
* fix(npm-proxy): keep large packages cacheable by using a single shard
* fix(npm-proxy): cache the archive so a large package is served, not refused
* fix(npm-proxy): read archives off the runtime, keeping only what types need
* fix(npm-proxy): charge a retained entry for what it allocates, not its bytes
* fix(npm-proxy): size retention for real packages and read the manifest back
* fix(npm-proxy): charge path bytes and pin the manifest read-back
* fix(npm-proxy): stop retaining past the budget instead of refusing the package
* fix(parser-py): keep first param when def main( line has trailing comment
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore: bump windmill-parser-wasm-py to 1.782.0
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(ata): fall back to the npm proxy when the CDN request fails outright
* fix(ata): surface proxy failures and guard the body read too
* docs(ata): state the proxy catch's constraint, not its history
* fix(ata): log a failed proxy d.ts fetch, which callers discard
* fix: carry the validated token endpoint with MCP OAuth credentials
get_or_refresh_mcp_client already resolved and checked the token endpoint on
both its cached and freshly-registered paths, then dropped the result. Keeping
it on McpClientCredentials lets the callers that post the client_secret there
connect to the address that was checked, and removes a second lookup they were
each doing on their own.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* refactor: hand out the token URL with the client pinned to it
Makes the pin unrepresentable-if-wrong rather than documented: the validated
target is private and reachable only through token_request, which returns the
URL together with the client pinned to the address it was checked against, so
a caller cannot pin one host and post to another.
Adds the test that was missing under the whole guard: that the pinned client
really does connect to the pinned address instead of resolving the host. The
accept loop is bounded, so a pin that stops working fails in seconds.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: keep the token endpoint private behind token_request
Leaving the URL public still allowed posting the client_secret to it on an
unpinned client, so the invariant was only documented. Both the URL and its
validated target are now private and reachable together, and the pinning test
resets the accepted socket to blocking so it does not read empty on macOS.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* test: drop the non-blocking reset from the pinning test
Linux hands back a blocking socket from accept regardless of the listener's
flag, and no runner here builds this crate for a platform that does otherwise.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to f8d523195e40fd1d740595dcab6ce5cdc1bdbf09
This commit updates the EE repository reference after PR #718 was merged in windmill-ee-private.
Previous ee-repo-ref: 729df45314c6f2168b44eddb6edea401b0495d6d
New ee-repo-ref: f8d523195e40fd1d740595dcab6ce5cdc1bdbf09
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
* feat(raw-apps): route in-browser npm installs through the npm proxy
* fix(npm-proxy): follow npm range semantics and cache packuments
* fix(npm-proxy): bound the packument cache by bytes and stream tarballs
* fix(npm-proxy): keep a v-prefixed pin exact and read the tarball once
* chore(raw-apps): bump the ui_builder pin to the npm-proxy installer
* feat(triggers): nested any_of / all_of filter groups
A trigger filter entry can now be a group — `{"any_of": [...]}` or
`{"all_of": [...]}` — nesting further entries, so criteria like
`A AND B AND (C OR D)` are expressible. Existing flat `{key, value}` lists keep
their meaning, combined by the trigger's `filter_logic` as before.
Filters are compiled once per connection: the set of top-level keys the whole
tree references is collected up front, so a message is parsed in a single
streaming pass that captures only those keys, instead of one full pass per leaf
filter as before. Filters that fail to parse are now logged rather than dropped
silently, since a nested group is easier to mistype than a flat entry.
The editor gains "Add group", rendering groups recursively with their own
AND/OR selector; Kafka and WebSocket triggers share it.
Fixes WIN-2345
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(triggers): drop empty filter groups instead of evaluating them
A group with no criterion cannot evaluate to a constant: true makes an `or`
filter list accept every message, false mutes an `and` list. Two clicks in the
editor ("Add group", save) produced one. Drop it when compiling so its siblings
stay in force, and reject at save time the filters the listener would otherwise
drop silently.
Also restore the item shape of `$ref`-typed arrays in the generated agent
schemas: the extractor only resolved refs at the property level, so moving
`filters.items` to a shared schema flattened it to a bare object. Resolving them
inside `items` too also recovers the shapes `initial_messages` and the MQTT
`topics` had already lost.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(triggers): name the offending entry when a nested filter is invalid
Serde's untagged error only reports that the outermost entry matched no
variant, whatever depth is actually wrong, which defeats the point of
validating a group at save time. Walk the tree instead and report the path.
Normalize the WebSocket editor's filters to [] on load, as the Kafka editor
does, so the list component can rely on an array.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(triggers): key filter rows by node so deletion keeps values aligned
The value editor seeds itself from `code` once, so an index-keyed row reused
for a different filter kept showing the deleted row's value.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* chore: bump ee-repo-ref after merging main
The merge pulled OSS code that needs EE symbols newer than the companion
branch's base, so the companion was merged with EE main too.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* perf(triggers): keep filter short-circuiting from materializing unread fields
The single-pass scan deserialized every referenced key before the boolean tree
ran, so an AND whose first leaf rejects the message still allocated the large
objects the later leaves name — the shape this feature exists for. Borrow the
wanted keys as raw slices during the scan and parse a field only when
evaluation actually reaches it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat(triggers): none_of filter group
Negation of a nested group, so a trigger can exclude what it must not react to
without inverting every other criterion. A key the message does not carry
satisfies it: there is nothing there to match.
Only groups can negate — the root's operator is the trigger's filter_logic
column, which has no value for it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat(triggers): address a nested field with a dotted path
`{path: "a.b.c", value: v}` alongside the existing `{key, value}`, so the common
case reads the way people write it instead of nesting the shape into the value.
A separate field rather than dots in `key`, which already means the top-level
field spelled that way — overloading it would resettle what existing triggers
over flattened payloads match.
Paths address objects only for now: a path through an array does not match
rather than guessing an element, and array containment stays on the value side.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs(triggers): mention none_of in the filter_logic description
Plus a test for the empty-path-segment rejection, which had none.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 78859aab0c6e78283ec8d2b37e8c410963afdc83
This commit updates the EE repository reference after PR #722 was merged in windmill-ee-private.
Previous ee-repo-ref: 0e42ba72ccc38a6b0a380f58afe0db36d284f4c9
New ee-repo-ref: 78859aab0c6e78283ec8d2b37e8c410963afdc83
Automated by sync-ee-ref workflow.
* fix(triggers): reject a criterion naming both key and path
The untagged enum takes such an entry as a `key` criterion and drops the
`path`, which is the silent-ignore the save-time validation exists to prevent.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* refactor(triggers): drop the label next to the key/path toggle
The toggle already shows which one is selected.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(triggers): reject an entry that combines a criterion with a group
Generalizes the key+path fix: the untagged enum settles a half-and-half entry
on the first variant that fits and ignores the rest, so a criterion carrying a
group key lost the whole subtree without a word.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* fix: bump the bundled DuckDB engine to 1.5.5
The 1.5.5 duckdb crate no longer hands back a 96-bit `rust_decimal`, so a
DECIMAL wider than that renders instead of panicking inside an `extern "C"`
frame — which, being unable to unwind, aborted the whole worker process and
left the job running as a zombie. `SELECT
'1234567890123456789012345678.9012345678'::DECIMAL(38, 10)` was enough.
Adapting to the crate's API: `Value` is now `#[non_exhaustive]` and gained
`UHugeInt` and `Geometry`, and `rust_decimal` became an optional feature that
the `decimal`/`numeric` argument path still needs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: address review findings on the duckdb bump
Run the FFI crate's own tests in CI: it is excluded from the workspace, so the
`cargo test --all` in backend-test never reached them and the new guard against
the worker-aborting DECIMAL would not have run. build_dev.sh now honors a
caller-pinned CARGO_TARGET_DIR so the test build reuses that compile instead of
building the bundled engine a second time.
Also pin UHUGEINT rendering, and correct the rust_decimal rationale —
`Decimal::new` is public without the feature, so the reason is that the feature
reproduces the exact binding the crate used to derive, not that nothing else can.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: address review nits on the duckdb bump
Name the unsupported DuckDB type rather than dumping the value, which may be
arbitrarily large or hold data that does not belong in an error message, and
say which column it came from.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* chore: pin the ee ref to the narrowed duckdb extension allowlist
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* chore: pin the ee ref to the verified duckdb extension allowlist
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* chore: pin the ee ref to the allowlist regression test
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 04dd9c5c352f04995cd0470400a877261f956561
This commit updates the EE repository reference after PR #716 was merged in windmill-ee-private.
Previous ee-repo-ref: fe7eb440a5bbae37774d3a96b69ab5c46c0b8936
New ee-repo-ref: 04dd9c5c352f04995cd0470400a877261f956561
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* fix(sdk): unbreak the JSR publish of the typescript client
`Sql` is `export type Sql = string`, but build.jsr.sh re-exported it as a
value, so `deno publish` fails type-checking with TS1205 under
isolatedModules. Every `v*` tag since has published nothing to JSR.
The npm build never noticed because it lists the same symbol as `type Sql`;
the two scripts keep separate copies of the export list.
Record both JSR-only constraints next to the list, since neither shows up
until a release tag runs publish.jsr.sh.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(sdk): scope the slow-types note to what deno actually rejects
Deno's fast check only rejects a return type it cannot trivially infer;
setClient, appendToResultStream and streamResult are all exported without
one and publish fine. The previous wording read as if the current list were
already non-compliant.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat(sdk): add cancelJob to the TypeScript client
The Python client has had cancel_job since forever; the TypeScript one had no
way to cancel a job at all. Wire the same jobs_u/queue/cancel endpoint, with a
default reason when none is given, and export it from both the named and
default exports of the npm package as well as the JSR one.
* chore: regenerate system prompts for cancelJob
check-system-prompts triggers on typescript-client/**, so the agent-facing SDK
reference has to carry the new function.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Tushar <tusharanshu18@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat: add unit tests for S3BufferedReader.read and improve read method implementation
* feat: refactor S3BufferedReader.read method and add unit tests for its functionality
* feat: implement peek() on S3BufferedReader with buffered reads
* fix(python-client): keep the read(size) contract and trim the test surface
Drop the duplicated `TestS3BufferedReaderRead` class from
`python-client/tests/wmill_client_test.py`: CI runs `pytest tests/` from
`python-client/wmill`, so that legacy manual harness never executes, and the
same assertions already live in `python-client/wmill/tests/test_s3_reader.py`.
Narrow that file to the four behaviours a future change could break, and make
the `bytes_generator` guard actually call `bytes_generator`.
Align `peek()` with `io.BufferedReader.peek`, which does at most one read on
the underlying stream, rather than looping until `size` bytes are buffered.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(python-client): hold read1 to one underlying read
read1 forwarded to read, so read1(-1) drained the whole object — the same
unbounded buffering this branch removes from read. Now that a buffer exists,
read1 can honour its own contract: fill only when the buffer is empty, then
serve from it.
Also treat read(None) as read(-1), per the BufferedReader contract, and pin
that read(0) does not pull from the stream: that holds only because the
drain sentinel is a negative size, and widening it to any falsy size would
reintroduce whole-file buffering.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(python-client): return from read1(0) without touching the stream
A zero-length read has nothing to serve, so pulling a chunk to satisfy it
both wastes a round trip and advances the stream. Guard it ahead of the
fill, and pin it with a chunk source that counts pulls.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Tushar <tusharanshu18@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: scope a fork's cloned app policy and custom path to its creator
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: gate a cloned anonymous app on the parent's own deployment rule
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs: state why a cloned anonymous app is gated more strictly than create_app
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* style: wrap an over-long comment line in clone_apps
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: clone an app's execution_mode unchanged
Forcing `publisher` on a cloned app was a speed bump rather than a boundary:
protection rules are workspace-scoped and are not cloned, so the fork's creator
can publish an anonymous app there with no rule in the way. It was also the one
policy field a deploy back to the parent carries verbatim, since `update_app`
recomputes the identity but writes the policy wholesale, so a fork's copy could
silently close the parent's public endpoint.
The identity rewrite is what closes the hole this addresses: the fork's endpoint
no longer runs as whoever the parent published it as.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix: ignore an app's run-as identity when comparing workspaces
`compare_two_apps` hashed the whole policy, so a fork whose apps were re-pointed
at their creator reported every one of them as changed. Nothing could clear those
entries: the deploy offers the target's current identity, the deployer's, or a
typed-in one, never the source's, so the difference survives however many times
the item is deployed. `script` and `flow` already compare no identity.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* chore: bump ee ref and refresh query cache
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* chore: bump ee ref
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* chore: bump ee ref
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* chore: bump ee ref
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* chore: bump ee ref
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 236115e11f074d86675aa5acdf5061dd3e64f43c
This commit updates the EE repository reference after PR #719 was merged in windmill-ee-private.
Previous ee-repo-ref: 62bc50118d09374b8a45756504520cfb6e5f0210
New ee-repo-ref: 236115e11f074d86675aa5acdf5061dd3e64f43c
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
* feat: version resource values with history, diff and restore
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: record resource versions in a trigger so direct writes are covered
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: show the selected version's value and tighten history write access
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* perf: gate resource version recording in trigger WHEN clauses
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat: clear a resource's past versions, and address review nits
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: restore the displayed version and keep author attribution on pooled writes
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: scope history to the selected workspace and gate clearing on ownership
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: gate restore on write access and clearing on the signed-in workspace
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* refactor(frontend): share the version-history row between script and resource drawers
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* perf: trim resource version history in the monitor sweep, not on write
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(frontend): match the script versions drawer shell for resource history
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* perf(frontend): highlight version values instead of mounting monaco
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(frontend): match the script drawer's code preview presentation
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: rank version trim in one windowed pass instead of a correlated delete
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* refactor(frontend): treat the newest version as current by position
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* perf: gate the resource version trim to an hourly sweep
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: unnest the version row action and correct the trim cadence docs
* perf: cap the history listing and use sets for reference lookup
* feat: warn when a resource is written more than 60 times a minute
* fix: lower the resource write advisory to 20 per minute
* fix: discard stale history loads and never diff against an unread value
* fix: correct the write advisory boundary and document the eviction lock
* fix: read history and the live value from one snapshot
* refactor: read the drawer's diff baseline from versions, not the live resource
* fix: open the history drawer with no version selected
* fix: disarm the clear confirmation and clear the pane when the selection moves
* fix: explain the missing diff and drop a guard that can no longer fire
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(smtp): explain why a test email failed instead of 'deadline has elapsed'
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(smtp): keep non-SMTP error codes and retire a stale test alert
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to f0df8b82c4c089d384423ed64b8504506084820d
This commit updates the EE repository reference after PR #721 was merged in windmill-ee-private.
Previous ee-repo-ref: 1ffaf3dea81e007c6c11146c1e12e97e83f5b938
New ee-repo-ref: f0df8b82c4c089d384423ed64b8504506084820d
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* fix(duckdb): cast columns in quicksearch so nested types can be previewed
DuckDB's `CONCAT` implicitly casts scalars but rejects nested types:
D SELECT CONCAT(' ', ['a','b']);
Binder Error: Cannot concatenate types VARCHAR and VARCHAR[] - an explicit
cast is required
Quicksearch concatenates every visible column, so one LIST, STRUCT or MAP column
makes a table impossible to preview — both the grid and its row count fail:
Binder Error: Cannot concatenate types VARCHAR, VARCHAR, BIGINT, ...,
VARCHAR[], ... and TIMESTAMP WITH TIME ZONE - an explicit cast is required
LINE 1: ... FROM "raw"."accounts" WHERE ($1 = '' OR CONCAT(' ', "id", ...
Every scalar in that list concatenates fine on its own — VARCHAR, BIGINT,
DOUBLE, BOOLEAN, DATE and TIMESTAMPTZ were each checked individually — so the
array column is the entire cause.
Cast each column in the predicate. The comparison is textual either way, so no
result changes, and the projection is untouched: casting there would change the
types the caller reads back. This follows the shape already used for MSSQL in
`mssql_needs_cast_for_eq`.
Both DuckDB quicksearch sites are covered, SELECT and COUNT. Fixing one leaves
the grid rendering while the row count still errors.
Tests include the live path: the Database Manager sends a
`-- WM_INTERNAL_DB_SELECT {...}` marker and the backend expands it, so the new
test drives that expansion with the real 27-column definition captured from a
failing job, `sync_id VARCHAR[]` included. It fails without the fix and passes
with it.
* fix(frontend): cast columns in the DuckDB quicksearch
Same defect as the Rust query builders, in the implementation that actually
runs. `make_select_query` / `make_count_query` in windmill-common have no callers
anywhere in the repo; the query the browser sends is built here.
DuckDB's CONCAT implicitly casts scalars but rejects nested types, and
quicksearch concatenates every visible column, so one LIST column makes a table
impossible to preview — both the page and its row count fail with
Binder Error: Cannot concatenate types VARCHAR, ..., VARCHAR[], ... and
TIMESTAMP WITH TIME ZONE - an explicit cast is required
The helper lives in select.ts and is imported by count.ts so the two cannot
drift, and both call sites are fixed: fixing only SELECT leaves the grid
rendering while the row count still errors.
* fix(duckdb): cast only list columns in quicksearch, leaving other SQL byte-identical
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* test(frontend): pin the DuckDB quicksearch column list byte-for-byte
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: stop the new raw app modal claiming AI is unconfigured before it knows
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: require a resolved workspace before trusting the loaded AI config
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: drop the unreachable token guard and point superadmins at instance settings
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: show the instance settings link to superadmins who are not workspace admins
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: never replace an in-flight indexeddb open, only a settled one
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat: keep a version history for session artifacts
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat: let the assistant browse an artifact's earlier versions
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat: pick an older artifact version from the preview panel
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* test(ai_evals): cover the change note the assistant writes on each edit
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: bound every indexeddb open, not only one told it is blocked
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>