mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-03 16:02:12 +00:00
42655ff5ef3e4eae8ff90c623683bb321ae7d06b
14850
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
42655ff5ef |
fix: memoize the resolved authed so one request resolves identity once (#11299)
* fix: memoize the resolved authed so one request resolves identity once Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test: pin that the memo preserves job token provenance Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
ea0b947644 |
fix: stop the chat repeating run results the card already shows (#11293)
* fix: tell the chat not to repeat run results the card already shows * fix: add the run result note on completion, background runs included * fix: skip the run result note on failed runs * fix: keep the run result note off background completions |
||
|
|
592ef73610 |
fix: keep a watched job's code when its completion update drops it (#11290)
Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
7dec3d9771 |
feat(db-manager): add a schema diagram view (#11030)
* feat(datatables): put a data table's connection under Postgres roles A data table backed by the instance database resolved to exactly one Postgres connection, `custom_instance_user`, for everyone who could reach it at all. There was no way to say this job reads, that one writes, this one never sees the salaries table. A data table role is now a real Postgres login on the cluster, defined once for the instance by a superadmin and named exactly as they named it. A script that declares `-- role analytics` connects as `analytics`, and Postgres decides what it may touch — grants are ordinary SQL. Windmill answers only "may this caller ask for this role", from the tenant lists on the data table entry: `u/alice`, `g/analysts`, `f/finance` or `*`. A data table with no `permissions` block behaves exactly as before. Everything that opens a connection on someone's behalf goes through one chokepoint, `get_datatable_resource_from_db`, which takes the identity explicitly and fails closed when there is none. The role logs in as itself — never `SET ROLE`, which a script could `RESET ROLE` its way out of. A fork's data table entry becomes a pointer at the workspace that governs it rather than a copy of it. The settings clone used to hand a fork a byte-identical entry naming the parent's database, which a fork admin could edit to grant themselves `admin` there; a pointer has nothing local to edit, and its tenants are evaluated as a member of the governing workspace, by email. `permissions` is stripped from the workspace export and ignored on import: tenants name principals of one workspace, and a settings push is not where an access decision should be made. Operations that see the whole database whatever the roles grant stay with the governing workspace's admins: editing the roles, a migration that declares none, and opening a replication stream for a Postgres trigger or capture. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR * fix(datatables): gate the paths that reach a whole database as admin Auditing what still resolved through the unchecked resolver turned up three that act for a caller and hand back the admin connection: `resolve_pg_source_checked` (behind schema export, the full-schema read, database creation, import and the forked-database drop), the connection test, and the schema snapshot a fork clone takes of its parent. On a data table under roles each let any workspace member — or a fork admin who is nobody in the governing workspace — read or copy the whole database whatever its roles grant. All three now require admin reach on the governing workspace. A dump taken under a restricted role would be a silently truncated copy rather than an error, so refusing is the only right answer for the copy paths. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR * fix(datatables): confine roles to the instance database, and stop a fork reaching the parent's bookkeeping A data table role is a login on Windmill's own Postgres. Nothing stopped a workspace admin putting a *resource-backed* data table under roles, at which point the executor dialled the host that resource names — one the admin chose — with the role's real cluster password, and `CONNECT` is granted to every registered instance database. Both ends now refuse: the permissions endpoint rejects the save, and the chokepoint refuses to substitute credentials on a non-instance entry rather than trusting the record it read. Two more places reached the governing database without answering to it. The initial-migration generator returned a `pg_dump` of the whole schema to any member. And the migration rename/delete cascade followed a fork's pointer into the parent, so a fork admin renaming or removing their own local entry relabelled or wiped the parent's `_wm_migrations` — after which the parent re-runs every migration from zero. The remote half is now skipped when the entry resolves into another workspace, which is also just correct: a fork renaming what it calls a data table changes nothing about the data table. Also: revoking a tenant now bounces the replication streams of every workspace holding an entry that resolves here, not only the governing one, so a fork's trigger stops rather than living on inside its open connection; the instance role catalog and the governing workspace's tenant lists are no longer returned to someone who cannot edit them; and the tenant rename dedup collapses non-adjacent duplicates, per role rather than once any role changed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR * fix(datatables): fail loudly where a role or a pointer can be left half-recorded Three ways the feature could end up in a state nobody could see or undo. Creating a role writes the cluster first and the catalog second, but the catalog write was an `UPDATE` that matched nothing when the instance Postgres settings row was absent — leaving a live login with a password nobody recorded: invisible to the catalog, un-recreatable because the name is taken, and un-deletable because there is no entry to delete. It now errors, so the operation is retryable once the row is restored. Deleting a workspace only nulls the fork lineage; the data table entries pointing at it are left resolving to nothing. Sweeping them is not an option — turning a pointer back into a copy would hand each fork the database outright — so the delete now names the data tables it stranded, and resolving one says which workspace is missing rather than reporting a data table this workspace never had. `InstanceDatatableRole` derived `Debug` while holding a Postgres password; it is now hand-written so `{:?}` on the catalog cannot put a live credential in a log line. Adds the two branches the reviews found unpinned: a caller who is not a member of the governing workspace at all, and `NoIdentity` — the compatibility path for an agent worker that predates this and sends no job id. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR * fix(datatables): unbreak two operator messages and two comments that described other code The two strings this branch added for states an operator hits once — the catalog write that matched nothing, and the delete that stranded a pointer — were collapsed from their multi-line form with the indentation left in, so both rendered with a fourteen-space gap mid-sentence. `list_datatables` claimed to report a chain it cannot follow and then dropped it; it does drop it, and the comment now says why that is the right place to stay quiet. The non-superadmin check in `edit_datatable_config` was introduced as also covering references, which it does not and need not: `reference` is overwritten from the stored entry for every caller before the check runs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR * fix(datatables): serialize role catalog mutations, and state each helper's authorization contract The catalog is one JSON document, so create, rename, enable and delete are all read-modify-write. Two concurrent creates read the same snapshot, both succeed in the cluster, and the second write drops the first — leaving a live Postgres login with a password nobody recorded, which is the exact state the delete path exists to prevent. Every mutation now runs in one transaction holding an advisory lock across the read, the cluster DDL and the write, so a lost update cannot happen and a failure rolls the whole thing back. The DDL helpers take that transaction rather than the pool, which is what makes the lock cover them. Their statements moved off `sqlx::raw_sql`: the simple protocol is only needed for genuinely multi-statement SQL, and its future is not `Send`, which an axum handler holding the transaction requires. Each of these is one statement anyway. The new cross-crate surface now says what callers must do. `read_role_catalog` returns plaintext credentials; `create`/`rename`/`set_login`/`drop_instance_role` and `converge_connect_grants` mutate cluster-wide state; `read_datatable_entry` reads a workspace's raw config. All of them are superadmin-gated by their current handlers, but nothing said so at the definition, which is where the next caller looks. Also: the roles table reloads after a failed login toggle instead of leaving it claiming a flip that did not land; the rename affordance is the design-system `Button`, not a raw one; and `resolve_datatable_pg_as_caller` drops a `role` parameter no caller ever filled — browsing resolves as the data table's default until the database manager grows a picker. Why role passwords stay a plain `String` while the instance user's password beside them is a `StringOrSecretRef`, asked three times across reviews: that one is a secret ref because an operator supplies it and may want it from their own backend, while these are minted here and never entered by anyone, so there is nothing for a ref to point at. Encrypting generated secrets at rest is a separate change that would take the replication password with it. Now said at the field. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR * fix(datatables): give the role catalog its own row, out of reach of the config machinery Putting it inside `custom_instance_pg_databases` was the wrong call, and it cost two ways. The catalog serializes a generated Postgres password per role, and that row is the operator-facing instance config, so the passwords reached `get_instance_config` and its YAML editor — a live cluster credential in a response body, a UI field and any log of either. Worse in the other direction: `to_settings_map` strips the catalog, so a full-row upsert of that key writes the row back without it and the catalog is gone, while the cluster keeps every login it described. `custom_instance_replication_pwd` is the precedent and says exactly why — a generated secret, written only by the server, never operator-authored, hidden so the config machinery cannot read, rewrite or drop it. The catalog is the same thing, so it now has the same shape: `datatable_roles`, in `HIDDEN_SETTINGS`, `PROTECTED_SETTINGS` and the agent-worker denylist. No redaction to keep in step with three code paths, and no way for a neighbouring write to take it out. Two races on the same shared documents. `edit_datatable_config` read the stored data tables outside its transaction and then wrote the whole `datatable` document, so a permissions save committing in between was silently rolled back; it now reads under `FOR UPDATE`. And `set_datatable_permissions` validated role ids against the catalog before opening its transaction, so a deletion in between let it write a deleted role back — including as the default, which every later job then fails on; it now holds the catalog lock and the settings row across validation and write. Completes the authorization contracts the previous commit claimed but did not finish: `read_datatable_entry` (which it named and missed), `resolve_governing_datatable`, whose whole job is to answer for a workspace the caller may not belong to, and `converge_connect_grants_with`, which had not inherited its wrapper's. Also the generic Python SDK reference: `_format_py_params` learned the bare `*` last time, but `extract_py_functions` is a second formatter and still rendered `datatable(name, role)`, so code written from that page passed a keyword-only argument positionally. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR * fix(datatables): make the concurrency test pin the handlers, and the contracts describe what is enforced The concurrency test reimplemented the read-modify-write inline, so deleting the lock from all three handlers left it green — it pinned Postgres, not the code it was written for. It now drives `create_datatable_role` twice concurrently and asserts the catalog kept both names. Checked the way the last one should have been: removing the lock from the handler makes it fail with "wmtest_a_… is a live cluster login the catalog forgot". The contracts added last commit were stricter than this PR's own callers, which is worse than none — the next reader sees a rule already broken and learns to ignore it. `read_role_catalog` said superadmin-only while two of its four callers are open to any workspace member, and `converge_connect_grants` said superadmin while `set_datatable_permissions` reaches it as a workspace admin. Both were fine on substance: the rule that actually holds is about the credential never reaching a response, log, audit record or export, not about who may call. They now say that. `read_datatable_entry` gets the same treatment rather than the one the earlier message claimed for it: it is the primitive every resolution goes through, so it is deliberately open, and what must not escape is `permissions` — it names the governing workspace's users, groups and folders. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR * fix(datatables): close the last ways a role or a pointer can be left pointing at nothing The raw settings readers hand back whatever is in the row, so moving the catalog into its own `global_settings` key protected the config machinery and left `GET /settings/global/datatable_roles` and the settings listing returning every live password. Both now filter that one key. The neighbouring `custom_instance_replication_pwd` has the same shape and is not touched here: it predates this and widening the fix to it is a decision about an operator workflow, not a consequence of this change. Three ways a save could leave something resolving to nothing: A permissioned data table could be moved to a PostgreSQL resource. The block was carried across as a server-owned field, the runtime refuses roles on a resource-backed table, so the save succeeded and every job afterwards failed. Refused instead — turning roles off first is one step, and it keeps discarding an access decision something somebody chose. Renaming a governing data table left every fork pointing at the old name: the data table disappears from their pickers and their jobs stop, with nothing in the renaming workspace to suggest why. The rename now follows into the pointers in the same transaction. Deleting one cannot be followed the same way, so it is reported instead — the response names what it stranded, the way deleting a workspace does, and the fork's own error already says which workspace is gone. Also: `ensure_instance_db_grant_options_unchecked` claimed superadmin while the permissions handler reaches it as a workspace admin (the same class fixed last commit, one instance missed); the role entry kept an `instance_config_schema` derive it no longer needs; `write_role_catalog` was the one writer of that table not stamping `updated_at`; and the concurrency test dropped its roles only on success — a failing run is exactly the one that creates them without recording them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR * refactor(datatables): put the role catalog in its own table, not in global_settings Five findings across three rounds were all the same choice. A set of live Postgres credentials was living in `global_settings`, which has generic read, list, write, config-export and CLI round-trip paths that know nothing about what they carry: the passwords reached the instance config and its YAML editor, a full-row upsert of a neighbouring key erased the catalog, `GET /settings/global/{key}` and the settings listing returned them raw, and this round the redaction that fixed the last two turned `wmill instance push` into something that wipes every password — a fix breaking the assumption the previous fix made. `POST /settings/global/datatable_roles` could also empty it outside the lock. The approved plan offered a table or `global_settings`, so this is the other option it already allowed rather than a new design. `datatable_role` is a table: no generic settings path can read it, list it, export it, write it or round-trip it, so none of the five needs a guard. The redaction, the hidden/protected/agent-denylist entries and the JSON document all go with it. One row per role also removes the read-modify-write the concurrency work was about: two concurrent creates are two inserts, and the unique index on `name` is what settles a collision. The advisory lock stays for the one window rows do not cover — `CREATE ROLE` is invisible to another transaction until commit, so without it both creates pass their `pg_roles` check. Also from this round: rename mappings are checked against the configuration they claim to describe, since fork pointers are rewritten from them — a caller could otherwise submit `main -> missing` against an unchanged config and repoint every fork of `main` at a name nothing has, and `A -> B` plus `B -> C` moved what pointed at `A` all the way to `C`. And the warning naming forks a delete stranded reached the response but not the screen: both the data table settings save and the workspace delete now show it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR * fix(datatables): validate a rename against the save it describes, and re-check under the locks Three from the round, all about deciding on state that could already have moved. A permission save resolved the data table and checked it was instance-backed before taking any lock, then wrote under one. A config save committing in between could move the table onto a PostgreSQL resource — recreating exactly what the transition guard refuses — or rename it, in which case the write targeted a key that no longer existed and reported success having changed nothing. It now re-resolves and re-checks on the locked state. Rename validation checked that the source existed before and the target existed after, which still accepts `main -> decoy` against a save that keeps both: every fork of `main` then follows onto a different data table, silently, because it keeps resolving. The rule is now the actual old-to-new key transition — a source may only survive if another rename took its name, and a target may only pre-exist if another rename freed it. That also stops two sources sharing one target, and it admits a swap, which the previous guard refused: `datatables` is keyed by name, so a swap cannot be done one save at a time, and refusing it was a regression against main. The pointer cascade now runs in two passes through a temporary name, the way the migration cascade one layer down already handles the same shape, so `A -> B` with `B -> C` moves each pointer once from what it named before the save. The tenant mutators say what they are for: they write an access decision for any workspace named, with an arbitrary mutation, and exist for the transaction that frees or renames a principal. Editing a decision on purpose belongs in the permissions endpoint. Carried in the same change: the stranded-fork list is a field rather than a phrase to grep out of a success string; the pointer cascade matches with `EXISTS` instead of a `LIKE` over the whole document, so a workspace whose pointers name something else is not rewritten to a byte-identical value under an exclusive lock; and `InstanceDatatableRole` drops the serde derives left over from the JSON document, one of which would emit `pwd`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR * fix(datatables): cascade on the leave route that is used, gate migrations before the admin connection, and drop a role atomically The tenant cascade on leaving went onto `/users/leave`. The UI and the generated client call `/workspaces/leave` — a different handler in a different crate with the same name — which deleted the membership and left `u/<username>` in the tenant lists. Leaving and rejoining therefore restored the access the leave was supposed to end, and a later account taking the username would have inherited it. The regression test drives the route the client actually calls; without the fix it fails with "leaving kept the tenant". The migration endpoints authorized too late. `run_datatable_migrations` opened the data table's admin connection, created `_wm_migrations` and read it before reaching the per-migration role check — so with nothing pending, nothing was checked at all. Rollback returned before its check when nothing was applied, and the status endpoint had none. All three now ask, before any connection is opened, whether the caller can reach the data table as any role at all; which role a given migration runs as is still decided per migration, and by the executor after that. Deleting a role committed the cluster drop and the catalog row, then swept the tenant lists in separate transactions. A sweep failing part-way left workspaces naming a role nothing can connect as, while the retry answered `NotFound` because the catalog entry was already gone. The sweep now runs in the same transaction, so the drop, the row and every tenant list commit together. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR * fix(datatables): refuse to copy a data table that is under roles pg_dump carries no roles and the import runs with --no-privileges, so a copied data table arrives owned by the admin connection with no GRANT for any role. The settings clone brings `permissions` across, so the fork's tenants pass Windmill's check, connect as the role they were given, and are denied by Postgres on everything: an entry that reads as configured and answers nothing. Refuse the copy — in the import endpoint before any data moves, and in the fork path the CLI takes. Replaying the source's owners and ACLs into the clone is what lifts this, and is a change of its own. Dropping `permissions` from the copy instead would be the unsafe half, since the copy holds the parent's rows. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR * fix(datatables): refuse the clone's database too, not only its data A clone is two endpoints: `create_pg_database` then `import_pg_database`. Only the second refused a data table under roles, so a fork asking to clone one created and registered an empty `wm_fork_…` instance database and then failed — and nothing collects it, since `drop_forked_datatable_databases` only drops entries carrying `forked_from` and no entry names this one. Refuse in both, so the clone stops before a database exists. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR * nit worker error msg * fix pg_dump stuck on version 17 on nix * fix(datatables): refuse a malformed role annotation instead of ignoring it `-- Role operator`, `-- role operator;` and `-- role operator -- why` all failed the annotation parser's exact-match rule, so the query fell through to the data table's default role and ran, silently, under a login the author did not choose. Naming a role exists precisely to not do that. A leading comment whose first word is `role` is now an annotation attempt: the keyword matches case-insensitively, one trailing `;` is tolerated, and anything else is an error naming the line. Only callers that already know the target is a `datatable://` reference ever run this, so ordinary SQL keeps its comments. Also bumps the dev shell's postgres client to 18 — it trailed the server the dev database runs, which takes out every data table export, clone and fork-with-data. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR * fix(datatables): refuse a malformed role query string instead of ignoring it `?Role=analytics`, `?role=` and `?x=1&role=…` all fell through the reference parser's exact-match rule, so the connection resolved to the data table's default role and ran under a login the caller never asked for — the URI half of the same trap as a malformed `-- role` annotation. The key now matches case-insensitively, and anything else in the query string is an error naming it; `role` is the only parameter a reference takes. Callers that only need the entry keep a lenient `datatable_ref_name`, since they never act on the role. The DuckDB `ATTACH` parser propagates it rather than attaching under the default. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR * fix(datatables): carry the role annotation into the row_to_json retry The retry rebuilds its SQL from `pruneComments(code)`, so the leading comment block never reached the second attempt — and with it the `-- role <name>` line that decides which login the query runs as. The retry connected as the data table's default role instead, so a query the first attempt was denied could succeed on the second, reported as "recovered with the row_to_json fix". Carry the leading comment block over. The retry itself is unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BjfMkJyKzodxkobqGZ6Lqb * chore(datatables): don't mount the roles UI until the ACL editor lands Enforcement ships first. The permissions drawer is what turns roles on, and the catalog section is what creates them — both are only useful once there is a way to grant a role the privileges it needs, which arrives with the ACL editor. Left mounted they would offer a feature whose other half does not exist. The two components are complete and reviewed; only their call sites here are commented out, with a note pointing the follow-up PRs at them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BjfMkJyKzodxkobqGZ6Lqb * fix(datatables): honour `-- role: x`, and fix the DuckDB attach test Two review findings, both real. `attach_datatable_parses_name_and_role` never compiled: `parse_attach_datatable` returns `Result<Option<_>>` now and one call site kept a single `unwrap`. Its `?Role=analytics` case also asserted a refusal, contradicting the parser in the same commit, which matches the key case-insensitively. Replaced with the cases that are genuinely malformed, and a positive one for the cased key. `-- role: analytics` fell through to the default role — the silent fallback the strict parser exists to remove, for the spelling most likely to be typed. The keyword now accepts an optional colon, attached or spaced, while a word that merely starts with it (`rolebased`) is still not an attempt. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BjfMkJyKzodxkobqGZ6Lqb * fix(datatables): clone a fork's pointer instead of failing after the copy Forking a fork with cloning left an orphan database. The preflight resolves the pointer and sees the governing entry, so both endpoints ran and filled the new database; `apply_forked_datatable` then refused the inherited pointer and rolled the fork back, stranding a registered `wm_fork_*` that no entry names and whose name blocks the retry. Refusing earlier would have been the smaller change, but forking a fork and cloning worked before pointers existed, so it would trade an orphan for a regression. Resolve what the pointer names and write the terminal entry the clone needs: the whole `database` object rather than a patch of its `resource_path`, since a pointer has none, and `reference` removed with it. Also accepts `-- role=x` and `-- Role = x`, two more spellings that fell through to the default role. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BjfMkJyKzodxkobqGZ6Lqb * fix(datatables): refuse to roll back the catalog while roles exist The down migration dropped the table and left every role behind: live Postgres logins whose passwords only that table carried, so after a revert Windmill could neither use, disable nor delete them, and re-applying could not recreate them because the names were taken. Cleaning up here is not possible either — dropping a role means reassigning what it owns in every instance database, and a migration runs in one — so it now refuses while the catalog is non-empty and says to delete the roles through instance settings, which does the cluster work. Also enforces the instance-only invariant the resolved-pointer clone relies on rather than only asserting it in a comment. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BjfMkJyKzodxkobqGZ6Lqb * refactor(datatables): settle clonability in one place, before anything is created A clone is three stages a workspace apart — `create_pg_database`, then `import_pg_database`, then `apply_forked_datatable` inside the fork transaction. Only the third can roll back, and `CREATE DATABASE` is not transactional, so any refusal that lives there strands a registered `wm_fork_*` that no entry names and whose name blocks the retry. That orphan has now been fixed three times, most recently reintroduced by a guard added one commit ago. Patching each new refusal into the first endpoint is not the fix; having two places that can refuse is. `ensure_datatable_is_clonable` now answers every reason a copy can be refused and returns what it resolved, and the stage that writes the entry only does the work. Also takes an ACCESS EXCLUSIVE lock before the rollback guard counts, so a role created concurrently cannot slip between the check and the drop. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BjfMkJyKzodxkobqGZ6Lqb * fix(datatables): let a retried clone reclaim its own leftover database A clone creates its target database one request before it copies into it, and the fork that would name it is written a request after that. Any failure in between — a pg_dump error, a bad restore, a dropped connection, the source's roles changing mid-flow — left a registered `wm_fork_*` that no entry names, and every retry then failed on its name. This predates data table roles. `create_pg_database` now reclaims such a leftover before creating: only a `wm_fork_*` database Windmill registered as a data table database and that no data table or ducklake entry names, in any workspace, archived ones included. The drop never terminates connections, so a clone still copying into it makes the reclaim fail instead of being cut off. It is limited to callers who administer the source — reaching it is not enough, since on a data table without roles every member reaches it — and anyone else gets the refusal an existing database always got. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Revert "fix(datatables): let a retried clone reclaim its own leftover database" This reverts commit |
||
|
|
25c8bfaab4 |
chore(main): release 1.817.0 (#11268)
* chore(main): release 1.817.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>v1.817.0 |
||
|
|
841f7e1436 |
fix(cli): sync workspace dependency files on Windows and fix CLI tests (#11288)
* fix(cli): sync workspace dependency files on Windows Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(cli): pin workspace dependency path parsing on both separators Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(cli): classify workspace dependency files on either separator Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(cli): check out the fork deploy clone without autocrlf Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
732214361f |
fix: stop the raw app editor stealing focus in ai sessions (#11287)
* fix: preserve raw app editor focus during ai file updates * fix: sync ai file edits through the existing files effect * docs: explain raw app file synchronization dependency * fix: avoid echoing raw app files back into the editor * fix: synchronize raw app file operations through one effect * fix: stop session draft sync from re-applying the editor's own edits Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: open raw app files without taking focus and follow idle ai edits Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep selecting raw app documents that live outside the app files Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: tie a pending raw app editor focus request to the picked file Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore: pin ui_builder to 60b6904 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: always send explicit raw app file replacements to the editor Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Guilhem Lemouel <guilhemlemouel@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
02006f364a |
fix: keep a cancel that lands after a worker last read its queue row (#11246)
* fix: keep a cancel that lands after a worker last read its queue row Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: take the cancellation from the queue row the completion deletes Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test: wait for the completion to block on the row instead of a timer Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * refactor: settle a missed cancel from the deleted queue row alone Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
7d0f2ce3bf |
fix: size the AI chat output budget by model, not provider (#11286)
* fix: size the AI chat output budget by model, not provider Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep self-hosted open-weight models on the fallback output budget Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore: point ee-repo-ref at the free tier output clamp change Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: treat codestral as open-weight for the output budget Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore: update ee-repo-ref to 559a9ff78ea03439553cf2b412e73765762e9013 This commit updates the EE repository reference after PR #821 was merged in windmill-ee-private. Previous ee-repo-ref: 953f7c1cc9a70b740d5d1b129cb1401950854b02 New ee-repo-ref: 559a9ff78ea03439553cf2b412e73765762e9013 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> |
||
|
|
efa7a0a70a |
feat: allow workspace-scoped dynamic custom tags (#11283)
* feat: check dynamic worker tags on the tag they resolve to Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test: pin the tag check after a flow preprocessor resolves the tag Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep tied tag placeholders exact and drop an unresolvable flow tag Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: describe tied tag placeholders in the custom tags editor Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: check workflow-as-code task tags on the tag they resolve to Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: judge a preprocessed flow's tag as written when it is run Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: check a preprocessed flow's own tag, as written only where it reads args Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: explain dynamic tags in the worker tag picker Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: look up worker availability for typed static tags Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: poll worker availability with the tag list as it is at each run Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: allow workspace-scoped dynamic custom tags Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test: pin the $workspace lookup for scoped custom tag patterns Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: keep a tag listed with its own scope inside it past matching patterns Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: warn when a scoped custom tag is also listed for every workspace Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
e7ba5954b6 |
feat: add workspace mentions to AI composer (#11264)
* feat: add workspace mentions to AI composer * fix: ignore embedded mention text in composer * fix: sync only standalone composer mentions * fix: keep punctuation-adjacent mentions active * fix: use unicode mention boundaries * fix: handle unicode mention prefixes * fix: avoid rescanning mention prefixes |
||
|
|
faf7b22be0 |
fix: apply token path scopes to the native trigger list (#11281)
Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
586699c493 |
fix: apply SSRF validation to workspace webhook URLs (#11285)
* fix: apply SSRF validation to workspace webhook URLs Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test: assert edit_webhook refuses a private webhook URL Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: stop the webhook sender following redirects past the SSRF check Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
9ad2c91ddb |
fix: enforce token path scopes on GET /raw_apps/list (#11284)
* fix: enforce token path scopes on GET /raw_apps/list Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: apply the raw app scope filter before the page limit Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test: cover the bare prefix path in the raw app scope test Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
58217006ec |
fix: stop cutting DeepSeek chat turns off mid-thought (#11282)
* fix: stop cutting DeepSeek chat turns off mid-thought Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test: pin the Anthropic max_tokens stop as a turn failure Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: drop the send-failure prefix from the output-limit toast Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
46206a2787 |
refactor(ai-chat): assemble the global prompt and tools in one place (#11252)
* refactor(ai-chat): assemble the global prompt and tools in one place Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor(ai-chat): derive assembly opts, harden omission tests Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
69fafb9262 |
feat: check dynamic worker tags on the tag they resolve to (#11271)
* feat: check dynamic worker tags on the tag they resolve to Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test: pin the tag check after a flow preprocessor resolves the tag Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep tied tag placeholders exact and drop an unresolvable flow tag Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: describe tied tag placeholders in the custom tags editor Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: check workflow-as-code task tags on the tag they resolve to Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: judge a preprocessed flow's tag as written when it is run Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: check a preprocessed flow's own tag, as written only where it reads args Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: explain dynamic tags in the worker tag picker Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: look up worker availability for typed static tags Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: poll worker availability with the tag list as it is at each run Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: warn on custom tags with nothing fixed at their start or end Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: open the broad custom tag warning from the keyboard Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
5798bd07e7 |
build derived release images from this run's image digest, not :dev (#11280)
* fix: build derived release images from this run's image digest, not :dev Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: scope the digest comment to jobs that build from, extract or retag Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
9e58f9a20f |
chore(security): resolve Dependabot alerts in backend/Cargo.lock and windmill-parser-wasm/Cargo.lock (#11182)
Targeted dependency bumps only; no behavior change intended. Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
9936673ee6 |
fix: clear the impersonation token from session storage on logout (#11274)
Impersonating a service account stashes the impersonator's own auth token under `pre_impersonation_token` in sessionStorage so the banner can restore it. Ending impersonation removed it, but logging out did not: `clearUser()` only reset the in-memory stores, leaving a usable token readable for the lifetime of the tab. Remove both impersonation keys as part of `clearUser()`, in their own try/catch so a storage failure (the opaque-origin iframe shim path) cannot skip the server-side logout. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
4d12ea4614 |
feat: deploy from the UI to a workspace on another instance (#11245)
* feat: deploy from the UI to a workspace on another instance Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep the remote deploy proxy from being spent by a link Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: key remote deploy proxy URLs instead of a global client header Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep the remote deploy proxy key out of logs and restricted hands Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: serialize remote deploy connect with account and key changes Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: key remote deploy tokens to the account and connect by signing in Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: bind remote deploy connect to its target and order its locks Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: serialize remote deploy connect with target changes Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: list every lock remote deploy connect takes in auth-surface Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: never wait on the membership lock in remote deploy connect Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep a superseded target response out of the settings form Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: void stale remote deploy tokens on read instead of locking in connect Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: void remote deploy tokens older than the last target change Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: order remote deploy connections by when their connect started Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: bind stored remote deploy tokens to the membership and target they were connected under Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: answer remote deploy connect without settings as no target Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
2699d5065e |
fix: check workflow-as-code task tags against CUSTOM_TAGS (#11273)
* fix: check workflow-as-code task tags against CUSTOM_TAGS Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: resolve and check every WAC child before the parent parks Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: build parent-code WAC children at push time, not in the pre-pass Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
2a40f25bda |
fix: add missing credential fields to settings redaction lists (#11272)
* fix: redact Azure and AWS credential fields in instance settings Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: redact the GitHub App private key in the settings change log Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
965e8b0c23 |
chore(main): release 1.816.0 (#11237)
* chore(main): release 1.816.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>v1.816.0 |
||
|
|
cbc6c9d454 |
fix: address AI sessions by id in the sessions page URL (#11266)
* fix: address AI sessions by id in the sessions page URL Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: title untitled sessions in the delete and archive confirmations Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: keep the legacy session link rewrite out of preview iframes Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: drop legacy session links on cross-family workspace switches Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
787b7a6bcc |
Revert "feat(auth): 2 h login links and a click-to-sign-in page for emailed ones (#11203)" (#11267)
This reverts commit
|
||
|
|
9f2685d788 |
perf: retire the legacy pre-partitioning audit table (#11240)
* perf: index audit logs by operation and limit each audit table on its own Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * perf: key the audit operation index on id with a trailing timestamp Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * perf: index the legacy audit table by operation too Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * perf: drop the legacy audit index and per-table limit from this PR Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * perf: build the audit operation index in a background migration Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * perf: retire the legacy pre-partitioning audit table Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: bound the legacy audit retirement's lock waits and keep the backfill guard Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: bound the audit index parent's lock wait and retry background steps Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore: update ee-repo-ref to 5370ae3a95a3dc10171f72da74286a654996eee6 This commit updates the EE repository reference after PR #818 was merged in windmill-ee-private. Previous ee-repo-ref: aa367619a970de231f5c10cad470d9d42169fff8 New ee-repo-ref: 5370ae3a95a3dc10171f72da74286a654996eee6 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> |
||
|
|
3b8c3264ec |
perf: index audit logs by workspace and operation (#11235)
* perf: index audit logs by operation and limit each audit table on its own Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * perf: key the audit operation index on id with a trailing timestamp Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * perf: index the legacy audit table by operation too Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * perf: drop the legacy audit index and per-table limit from this PR Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * perf: build the audit operation index in a background migration Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: bound the audit index parent's lock wait and retry background steps Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
4cbc944c0b |
perf: keep the sessions page fast with many ai sessions (#11224)
* perf: skip whole-document menu lookup on pointerdown when closed Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * perf: load only a session's own chats in its chat history Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * perf: stop mounting a chat per listed session on the sessions page Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep unsent input, init and chat rotation safe from runtime eviction Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor: cap mounted chat panes instead of evicting session runtimes Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor: read only message count and error for sessions without a runtime Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep a chat pane mounted while its composer reads attachments Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: hold a session's first send until its chat has been restored Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: queue a second send behind one waiting on the chat restore Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: queue raw-app inline prompts behind a send waiting on the restore Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep a chat pane mounted while its send waits on the restore Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: preserve session chat drafts across async updates --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
b7425443e1 |
fix: guard data table migration routes against operators and unauthorized authors (#11243)
* fix: refuse operators and unauthorized authors on data table migration routes Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: check the replaced definition on migration upsert and skip unchanged writes Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: let data table admins edit migrations whose annotation no longer parses Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep migration delete idempotent when a concurrent delete wins 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: Diego Imbert <70353967+diegoimbert@users.noreply.github.com> |
||
|
|
f39742ae32 |
chore(deps): bump actions/setup-python from 5 to 7 (#11256)
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5 to 7. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v5...v7) --- updated-dependencies: - dependency-name: actions/setup-python dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
6dc78049ba |
chore(deps): bump actions/setup-go from 2 to 7 (#11257)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 2 to 7. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/v2...v7) --- updated-dependencies: - dependency-name: actions/setup-go dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
dbac7cce99 |
refactor(ai-evals): drop two unreachable job routes from the fetch stub (#11251)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
49e0974c07 |
chore(deps): bump astral-sh/setup-uv from 5 to 7 (#11258)
Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 5 to 7. - [Release notes](https://github.com/astral-sh/setup-uv/releases) - [Commits](https://github.com/astral-sh/setup-uv/compare/v5...v7) --- updated-dependencies: - dependency-name: astral-sh/setup-uv dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
d340bec999 |
chore(deps): bump actions/setup-node from 3 to 7 (#11260)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 3 to 7. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v3...v7) --- updated-dependencies: - dependency-name: actions/setup-node dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
c637b06a54 |
feat: add per-model context window override in workspace AI settings (#11232)
* feat: add per-model context window override in workspace AI settings Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test: pin the instance ai_config context window validation Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor: name the table-only context window lookup for what it is Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: drop the per-row assumed-window hint from the model limits card Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: fold the model limit default into an inline reset button Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: give the per-model settings cards one reset control Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: enforce the documented output limit bounds on the AI config Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: show inherited model limits as placeholders, like model pricing Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: hugocasa <hugo@casademont.ch> |
||
|
|
d8ebbc06f5 |
fix: tell the user when a draft conflict has stopped their edits saving (#11241)
* fix: tell the user when a draft conflict has stopped their edits saving Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: give nothing up until the other version has actually loaded Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: ignore a reload the editor has moved on from, and settle saves before resolving Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: settle the key before a forced resolution, and cover quiesce with tests Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: let a resolution tell it belongs to an editor that is gone Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: end a conflict resolution when the editing session does, not the component Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: show the resource conflict in the fixed banner, and scope busy to its session Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep the conflict alert in editors no host wraps Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: free a reopened editor from a resolution the closed one left in flight Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: make switching workspace-specific versions a new conflict session Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep the local draft when a conflict resolution is abandoned Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: let a newer session outrank a resolution that outlived its editor Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: end a conflict resolution with the session that started it Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: reopen a conflicted editor on the version the server refused Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep a refused draft deletion a deletion Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: ungate the editor when a resolution loads the other draft Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: settle the draft key before reading the version to load Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: settle the key again after reading, for edits made under the read Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
5095456bb6 |
chore(security): bump jsonwebtoken to 10 (#11194)
* refactor(jwt): route JWK algorithm lookup through one helper Add `windmill_common::jwt::jwk_algorithm` as the single place that reads a JWK's `alg`, and make `guest_jwt::jwk_algorithms` use it. EE code will call the same helper, so the upcoming jsonwebtoken bump (which renames the field to `key_algorithm: Option<KeyAlgorithm>`) only has to touch the helper's body. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * chore(security): bump jsonwebtoken to 10 Resolves dependency alerts 193 and 344 (jsonwebtoken) and, for the windmill-parser-wasm lock, 341 (ring 0.16). - jsonwebtoken 8.3.0 -> 10.4.0 with the `rust_crypto` backend (10.x has no default crypto backend). `jwk_algorithm` now reads `key_algorithm` (a `KeyAlgorithm`, signing and encryption algorithms alike) and maps only the signing subset onto `Algorithm`; `guest_jwt::jwk_algorithms` refuses a key naming a non-signing `alg` (RSA-OAEP, ...) instead of treating it as alg-less. `decode_without_verify` moves off the removed `insecure_disable_signature_validation` onto `dangerous::insecure_decode`. - Workspace lock: only jsonwebtoken and `pem 1.1.1` (removed) change; the rust_crypto tree was already present. `ring 0.16.20` stays in backend/Cargo.lock because `gcp_auth 0.9.0` holds it. - windmill-parser-wasm lock: jsonwebtoken 8.3.0 -> 10.4.0 drops `ring 0.16.20` (jsonwebtoken was its only holder there), `spin 0.5.2`, `untrusted 0.7.1`, `base64 0.13.1`; `pem 1.1.1 -> 3.0.6` and `serde_json 1.0.143 -> 1.0.151` were forced by the new jsonwebtoken. - ee-repo-ref.txt -> 514eb5f5 (windmill-ee-private chore/ee-dep-bumps, on top of the previous ref d252afcc), which reads the JWK algorithm through `windmill_common::jwt::jwk_algorithm` and builds proto `KeyValue`s with `..Default::default()`. The opentelemetry 0.32 bump is deferred: tracing-opentelemetry 0.33 removed `OtelData`/`PreSampledTracer`, which the EE `otel_ee.rs` log bridge uses, and `otel` ships in every EE image (`ee_core`); that bridge needs a `Dispatch`-based rewrite first. Checks (SQLX_OFFLINE, EE files from 514eb5f5): cargo check --features all_sqlx_features; --features all_sqlx_features,private; --features enterprise,private,otel; cargo test -p windmill-common --lib jwt (22 passed). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(jwt): keep audience-bearing tokens and 8192-bit RSA keys working on jsonwebtoken 10 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * chore: update ee-repo-ref to 6f3161192ab0eba36a8630b92e6765a36a3326fc This commit updates the EE repository reference after PR #809 was merged in windmill-ee-private. Previous ee-repo-ref: 7dcc5741c2fa9f9997b3da085f1f18ffc1e446d6 New ee-repo-ref: 6f3161192ab0eba36a8630b92e6765a36a3326fc Automated by sync-ee-ref workflow. --------- Co-authored-by: Claude Fable 5.1 <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> |
||
|
|
fc7311c13e |
chore(dependabot): grouped weekly updates for all shipped manifests (#11189)
* chore(dependabot): grouped weekly updates for all shipped manifests
Until now only /frontend, /backend, /, and /python-client/wmill had
Dependabot version-update jobs; the other shipped manifests only ever
produced security alerts and never fix PRs. Frontend and backend also
received one PR per dependency.
Added (weekly, one group each with patterns ["*"]):
- /cli package-ecosystem "bun" (text bun.lock;
GA, enable-beta-ecosystems not needed)
- /windmill-yaml-validator npm (package-lock.json)
- /typescript-client npm (package-lock.json, devDependencies only)
- /multiplayer npm (package-lock.json)
Grouping of existing jobs:
- /frontend: frontend-deps (minor + patch) and frontend-majors (major),
so routine bumps land in one PR and majors can be reviewed on their
own without blocking it.
- /backend: cargo-deps (minor + patch); majors stay as individual PRs
because they usually need code changes.
open-pull-requests-limit is left at its default (5): with grouping each
manifest yields at most one or two PRs per run, so no override is needed.
Deliberately not covered:
- /rust-client: Cargo.toml is generated and gitignored
- /backend/parsers/windmill-parser-wasm: excluded from the cargo
workspace, needs nightly cargo
- /benchmarks/pulumi: being deleted in another PR
- /integration_tests: CI-only
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* chore: group dependabot security updates per manifest
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
|
||
|
|
ffe41ae150 |
chore(frontend): list runtime-imported packages under dependencies (#11184)
These packages are imported by code under src/ and ship in the browser bundle, but were
listed as devDependencies, which made Dependabot label their alerts "development" scope.
No version changes; the lockfile diff only flips dev flags. Shipped code is unchanged: the
only bundle difference is the package.json text that vite's `define: { __pkg__ }` inlines
into three chunks, and the content-hash file names that follow from it.
@sveltejs/kit stays a devDependency on purpose: its client runtime ships, but its advisories
are server-side and moving it would drag vite and its plugins out of dev scope too.
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
|
||
|
|
1b0f119c7b |
fix: check custom_instance_user can connect during instance database setup (#11244)
* fix: check custom_instance_user can connect during instance database setup Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: mark the custom_instance_user login ok before tearing the connection down Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
1f9aa45b9d |
refactor(ai-chat): remove the API catalog tools from the global chat (#11247)
* refactor(ai-chat): remove the API catalog tools from the global chat Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor(ai-chat): drop a stale comment from the benchmark fetch tests Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
e1e3692fbc |
feat: data table roles in the DB manager and raw apps (#11139)
* feat(datatables): put a data table's connection under Postgres roles A data table backed by the instance database resolved to exactly one Postgres connection, `custom_instance_user`, for everyone who could reach it at all. There was no way to say this job reads, that one writes, this one never sees the salaries table. A data table role is now a real Postgres login on the cluster, defined once for the instance by a superadmin and named exactly as they named it. A script that declares `-- role analytics` connects as `analytics`, and Postgres decides what it may touch — grants are ordinary SQL. Windmill answers only "may this caller ask for this role", from the tenant lists on the data table entry: `u/alice`, `g/analysts`, `f/finance` or `*`. A data table with no `permissions` block behaves exactly as before. Everything that opens a connection on someone's behalf goes through one chokepoint, `get_datatable_resource_from_db`, which takes the identity explicitly and fails closed when there is none. The role logs in as itself — never `SET ROLE`, which a script could `RESET ROLE` its way out of. A fork's data table entry becomes a pointer at the workspace that governs it rather than a copy of it. The settings clone used to hand a fork a byte-identical entry naming the parent's database, which a fork admin could edit to grant themselves `admin` there; a pointer has nothing local to edit, and its tenants are evaluated as a member of the governing workspace, by email. `permissions` is stripped from the workspace export and ignored on import: tenants name principals of one workspace, and a settings push is not where an access decision should be made. Operations that see the whole database whatever the roles grant stay with the governing workspace's admins: editing the roles, a migration that declares none, and opening a replication stream for a Postgres trigger or capture. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR * fix(datatables): gate the paths that reach a whole database as admin Auditing what still resolved through the unchecked resolver turned up three that act for a caller and hand back the admin connection: `resolve_pg_source_checked` (behind schema export, the full-schema read, database creation, import and the forked-database drop), the connection test, and the schema snapshot a fork clone takes of its parent. On a data table under roles each let any workspace member — or a fork admin who is nobody in the governing workspace — read or copy the whole database whatever its roles grant. All three now require admin reach on the governing workspace. A dump taken under a restricted role would be a silently truncated copy rather than an error, so refusing is the only right answer for the copy paths. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR * fix(datatables): confine roles to the instance database, and stop a fork reaching the parent's bookkeeping A data table role is a login on Windmill's own Postgres. Nothing stopped a workspace admin putting a *resource-backed* data table under roles, at which point the executor dialled the host that resource names — one the admin chose — with the role's real cluster password, and `CONNECT` is granted to every registered instance database. Both ends now refuse: the permissions endpoint rejects the save, and the chokepoint refuses to substitute credentials on a non-instance entry rather than trusting the record it read. Two more places reached the governing database without answering to it. The initial-migration generator returned a `pg_dump` of the whole schema to any member. And the migration rename/delete cascade followed a fork's pointer into the parent, so a fork admin renaming or removing their own local entry relabelled or wiped the parent's `_wm_migrations` — after which the parent re-runs every migration from zero. The remote half is now skipped when the entry resolves into another workspace, which is also just correct: a fork renaming what it calls a data table changes nothing about the data table. Also: revoking a tenant now bounces the replication streams of every workspace holding an entry that resolves here, not only the governing one, so a fork's trigger stops rather than living on inside its open connection; the instance role catalog and the governing workspace's tenant lists are no longer returned to someone who cannot edit them; and the tenant rename dedup collapses non-adjacent duplicates, per role rather than once any role changed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR * fix(datatables): fail loudly where a role or a pointer can be left half-recorded Three ways the feature could end up in a state nobody could see or undo. Creating a role writes the cluster first and the catalog second, but the catalog write was an `UPDATE` that matched nothing when the instance Postgres settings row was absent — leaving a live login with a password nobody recorded: invisible to the catalog, un-recreatable because the name is taken, and un-deletable because there is no entry to delete. It now errors, so the operation is retryable once the row is restored. Deleting a workspace only nulls the fork lineage; the data table entries pointing at it are left resolving to nothing. Sweeping them is not an option — turning a pointer back into a copy would hand each fork the database outright — so the delete now names the data tables it stranded, and resolving one says which workspace is missing rather than reporting a data table this workspace never had. `InstanceDatatableRole` derived `Debug` while holding a Postgres password; it is now hand-written so `{:?}` on the catalog cannot put a live credential in a log line. Adds the two branches the reviews found unpinned: a caller who is not a member of the governing workspace at all, and `NoIdentity` — the compatibility path for an agent worker that predates this and sends no job id. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR * fix(datatables): unbreak two operator messages and two comments that described other code The two strings this branch added for states an operator hits once — the catalog write that matched nothing, and the delete that stranded a pointer — were collapsed from their multi-line form with the indentation left in, so both rendered with a fourteen-space gap mid-sentence. `list_datatables` claimed to report a chain it cannot follow and then dropped it; it does drop it, and the comment now says why that is the right place to stay quiet. The non-superadmin check in `edit_datatable_config` was introduced as also covering references, which it does not and need not: `reference` is overwritten from the stored entry for every caller before the check runs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR * fix(datatables): serialize role catalog mutations, and state each helper's authorization contract The catalog is one JSON document, so create, rename, enable and delete are all read-modify-write. Two concurrent creates read the same snapshot, both succeed in the cluster, and the second write drops the first — leaving a live Postgres login with a password nobody recorded, which is the exact state the delete path exists to prevent. Every mutation now runs in one transaction holding an advisory lock across the read, the cluster DDL and the write, so a lost update cannot happen and a failure rolls the whole thing back. The DDL helpers take that transaction rather than the pool, which is what makes the lock cover them. Their statements moved off `sqlx::raw_sql`: the simple protocol is only needed for genuinely multi-statement SQL, and its future is not `Send`, which an axum handler holding the transaction requires. Each of these is one statement anyway. The new cross-crate surface now says what callers must do. `read_role_catalog` returns plaintext credentials; `create`/`rename`/`set_login`/`drop_instance_role` and `converge_connect_grants` mutate cluster-wide state; `read_datatable_entry` reads a workspace's raw config. All of them are superadmin-gated by their current handlers, but nothing said so at the definition, which is where the next caller looks. Also: the roles table reloads after a failed login toggle instead of leaving it claiming a flip that did not land; the rename affordance is the design-system `Button`, not a raw one; and `resolve_datatable_pg_as_caller` drops a `role` parameter no caller ever filled — browsing resolves as the data table's default until the database manager grows a picker. Why role passwords stay a plain `String` while the instance user's password beside them is a `StringOrSecretRef`, asked three times across reviews: that one is a secret ref because an operator supplies it and may want it from their own backend, while these are minted here and never entered by anyone, so there is nothing for a ref to point at. Encrypting generated secrets at rest is a separate change that would take the replication password with it. Now said at the field. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR * fix(datatables): give the role catalog its own row, out of reach of the config machinery Putting it inside `custom_instance_pg_databases` was the wrong call, and it cost two ways. The catalog serializes a generated Postgres password per role, and that row is the operator-facing instance config, so the passwords reached `get_instance_config` and its YAML editor — a live cluster credential in a response body, a UI field and any log of either. Worse in the other direction: `to_settings_map` strips the catalog, so a full-row upsert of that key writes the row back without it and the catalog is gone, while the cluster keeps every login it described. `custom_instance_replication_pwd` is the precedent and says exactly why — a generated secret, written only by the server, never operator-authored, hidden so the config machinery cannot read, rewrite or drop it. The catalog is the same thing, so it now has the same shape: `datatable_roles`, in `HIDDEN_SETTINGS`, `PROTECTED_SETTINGS` and the agent-worker denylist. No redaction to keep in step with three code paths, and no way for a neighbouring write to take it out. Two races on the same shared documents. `edit_datatable_config` read the stored data tables outside its transaction and then wrote the whole `datatable` document, so a permissions save committing in between was silently rolled back; it now reads under `FOR UPDATE`. And `set_datatable_permissions` validated role ids against the catalog before opening its transaction, so a deletion in between let it write a deleted role back — including as the default, which every later job then fails on; it now holds the catalog lock and the settings row across validation and write. Completes the authorization contracts the previous commit claimed but did not finish: `read_datatable_entry` (which it named and missed), `resolve_governing_datatable`, whose whole job is to answer for a workspace the caller may not belong to, and `converge_connect_grants_with`, which had not inherited its wrapper's. Also the generic Python SDK reference: `_format_py_params` learned the bare `*` last time, but `extract_py_functions` is a second formatter and still rendered `datatable(name, role)`, so code written from that page passed a keyword-only argument positionally. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR * fix(datatables): make the concurrency test pin the handlers, and the contracts describe what is enforced The concurrency test reimplemented the read-modify-write inline, so deleting the lock from all three handlers left it green — it pinned Postgres, not the code it was written for. It now drives `create_datatable_role` twice concurrently and asserts the catalog kept both names. Checked the way the last one should have been: removing the lock from the handler makes it fail with "wmtest_a_… is a live cluster login the catalog forgot". The contracts added last commit were stricter than this PR's own callers, which is worse than none — the next reader sees a rule already broken and learns to ignore it. `read_role_catalog` said superadmin-only while two of its four callers are open to any workspace member, and `converge_connect_grants` said superadmin while `set_datatable_permissions` reaches it as a workspace admin. Both were fine on substance: the rule that actually holds is about the credential never reaching a response, log, audit record or export, not about who may call. They now say that. `read_datatable_entry` gets the same treatment rather than the one the earlier message claimed for it: it is the primitive every resolution goes through, so it is deliberately open, and what must not escape is `permissions` — it names the governing workspace's users, groups and folders. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR * fix(datatables): close the last ways a role or a pointer can be left pointing at nothing The raw settings readers hand back whatever is in the row, so moving the catalog into its own `global_settings` key protected the config machinery and left `GET /settings/global/datatable_roles` and the settings listing returning every live password. Both now filter that one key. The neighbouring `custom_instance_replication_pwd` has the same shape and is not touched here: it predates this and widening the fix to it is a decision about an operator workflow, not a consequence of this change. Three ways a save could leave something resolving to nothing: A permissioned data table could be moved to a PostgreSQL resource. The block was carried across as a server-owned field, the runtime refuses roles on a resource-backed table, so the save succeeded and every job afterwards failed. Refused instead — turning roles off first is one step, and it keeps discarding an access decision something somebody chose. Renaming a governing data table left every fork pointing at the old name: the data table disappears from their pickers and their jobs stop, with nothing in the renaming workspace to suggest why. The rename now follows into the pointers in the same transaction. Deleting one cannot be followed the same way, so it is reported instead — the response names what it stranded, the way deleting a workspace does, and the fork's own error already says which workspace is gone. Also: `ensure_instance_db_grant_options_unchecked` claimed superadmin while the permissions handler reaches it as a workspace admin (the same class fixed last commit, one instance missed); the role entry kept an `instance_config_schema` derive it no longer needs; `write_role_catalog` was the one writer of that table not stamping `updated_at`; and the concurrency test dropped its roles only on success — a failing run is exactly the one that creates them without recording them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR * refactor(datatables): put the role catalog in its own table, not in global_settings Five findings across three rounds were all the same choice. A set of live Postgres credentials was living in `global_settings`, which has generic read, list, write, config-export and CLI round-trip paths that know nothing about what they carry: the passwords reached the instance config and its YAML editor, a full-row upsert of a neighbouring key erased the catalog, `GET /settings/global/{key}` and the settings listing returned them raw, and this round the redaction that fixed the last two turned `wmill instance push` into something that wipes every password — a fix breaking the assumption the previous fix made. `POST /settings/global/datatable_roles` could also empty it outside the lock. The approved plan offered a table or `global_settings`, so this is the other option it already allowed rather than a new design. `datatable_role` is a table: no generic settings path can read it, list it, export it, write it or round-trip it, so none of the five needs a guard. The redaction, the hidden/protected/agent-denylist entries and the JSON document all go with it. One row per role also removes the read-modify-write the concurrency work was about: two concurrent creates are two inserts, and the unique index on `name` is what settles a collision. The advisory lock stays for the one window rows do not cover — `CREATE ROLE` is invisible to another transaction until commit, so without it both creates pass their `pg_roles` check. Also from this round: rename mappings are checked against the configuration they claim to describe, since fork pointers are rewritten from them — a caller could otherwise submit `main -> missing` against an unchanged config and repoint every fork of `main` at a name nothing has, and `A -> B` plus `B -> C` moved what pointed at `A` all the way to `C`. And the warning naming forks a delete stranded reached the response but not the screen: both the data table settings save and the workspace delete now show it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR * fix(datatables): validate a rename against the save it describes, and re-check under the locks Three from the round, all about deciding on state that could already have moved. A permission save resolved the data table and checked it was instance-backed before taking any lock, then wrote under one. A config save committing in between could move the table onto a PostgreSQL resource — recreating exactly what the transition guard refuses — or rename it, in which case the write targeted a key that no longer existed and reported success having changed nothing. It now re-resolves and re-checks on the locked state. Rename validation checked that the source existed before and the target existed after, which still accepts `main -> decoy` against a save that keeps both: every fork of `main` then follows onto a different data table, silently, because it keeps resolving. The rule is now the actual old-to-new key transition — a source may only survive if another rename took its name, and a target may only pre-exist if another rename freed it. That also stops two sources sharing one target, and it admits a swap, which the previous guard refused: `datatables` is keyed by name, so a swap cannot be done one save at a time, and refusing it was a regression against main. The pointer cascade now runs in two passes through a temporary name, the way the migration cascade one layer down already handles the same shape, so `A -> B` with `B -> C` moves each pointer once from what it named before the save. The tenant mutators say what they are for: they write an access decision for any workspace named, with an arbitrary mutation, and exist for the transaction that frees or renames a principal. Editing a decision on purpose belongs in the permissions endpoint. Carried in the same change: the stranded-fork list is a field rather than a phrase to grep out of a success string; the pointer cascade matches with `EXISTS` instead of a `LIKE` over the whole document, so a workspace whose pointers name something else is not rewritten to a byte-identical value under an exclusive lock; and `InstanceDatatableRole` drops the serde derives left over from the JSON document, one of which would emit `pwd`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR * fix(datatables): cascade on the leave route that is used, gate migrations before the admin connection, and drop a role atomically The tenant cascade on leaving went onto `/users/leave`. The UI and the generated client call `/workspaces/leave` — a different handler in a different crate with the same name — which deleted the membership and left `u/<username>` in the tenant lists. Leaving and rejoining therefore restored the access the leave was supposed to end, and a later account taking the username would have inherited it. The regression test drives the route the client actually calls; without the fix it fails with "leaving kept the tenant". The migration endpoints authorized too late. `run_datatable_migrations` opened the data table's admin connection, created `_wm_migrations` and read it before reaching the per-migration role check — so with nothing pending, nothing was checked at all. Rollback returned before its check when nothing was applied, and the status endpoint had none. All three now ask, before any connection is opened, whether the caller can reach the data table as any role at all; which role a given migration runs as is still decided per migration, and by the executor after that. Deleting a role committed the cluster drop and the catalog row, then swept the tenant lists in separate transactions. A sweep failing part-way left workspaces naming a role nothing can connect as, while the retry answered `NotFound` because the catalog entry was already gone. The sweep now runs in the same transaction, so the drop, the row and every tenant list commit together. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR * fix(datatables): refuse to copy a data table that is under roles pg_dump carries no roles and the import runs with --no-privileges, so a copied data table arrives owned by the admin connection with no GRANT for any role. The settings clone brings `permissions` across, so the fork's tenants pass Windmill's check, connect as the role they were given, and are denied by Postgres on everything: an entry that reads as configured and answers nothing. Refuse the copy — in the import endpoint before any data moves, and in the fork path the CLI takes. Replaying the source's owners and ACLs into the clone is what lifts this, and is a change of its own. Dropping `permissions` from the copy instead would be the unsafe half, since the copy holds the parent's rows. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR * fix(datatables): refuse the clone's database too, not only its data A clone is two endpoints: `create_pg_database` then `import_pg_database`. Only the second refused a data table under roles, so a fork asking to clone one created and registered an empty `wm_fork_…` instance database and then failed — and nothing collects it, since `drop_forked_datatable_databases` only drops entries carrying `forked_from` and no entry names this one. Refuse in both, so the clone stops before a database exists. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR * nit worker error msg * fix pg_dump stuck on version 17 on nix * fix(datatables): refuse a malformed role annotation instead of ignoring it `-- Role operator`, `-- role operator;` and `-- role operator -- why` all failed the annotation parser's exact-match rule, so the query fell through to the data table's default role and ran, silently, under a login the author did not choose. Naming a role exists precisely to not do that. A leading comment whose first word is `role` is now an annotation attempt: the keyword matches case-insensitively, one trailing `;` is tolerated, and anything else is an error naming the line. Only callers that already know the target is a `datatable://` reference ever run this, so ordinary SQL keeps its comments. Also bumps the dev shell's postgres client to 18 — it trailed the server the dev database runs, which takes out every data table export, clone and fork-with-data. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR * fix(datatables): refuse a malformed role query string instead of ignoring it `?Role=analytics`, `?role=` and `?x=1&role=…` all fell through the reference parser's exact-match rule, so the connection resolved to the data table's default role and ran under a login the caller never asked for — the URI half of the same trap as a malformed `-- role` annotation. The key now matches case-insensitively, and anything else in the query string is an error naming it; `role` is the only parameter a reference takes. Callers that only need the entry keep a lenient `datatable_ref_name`, since they never act on the role. The DuckDB `ATTACH` parser propagates it rather than attaching under the default. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR * fix(datatables): carry the role annotation into the row_to_json retry The retry rebuilds its SQL from `pruneComments(code)`, so the leading comment block never reached the second attempt — and with it the `-- role <name>` line that decides which login the query runs as. The retry connected as the data table's default role instead, so a query the first attempt was denied could succeed on the second, reported as "recovered with the row_to_json fix". Carry the leading comment block over. The retry itself is unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BjfMkJyKzodxkobqGZ6Lqb * chore(datatables): don't mount the roles UI until the ACL editor lands Enforcement ships first. The permissions drawer is what turns roles on, and the catalog section is what creates them — both are only useful once there is a way to grant a role the privileges it needs, which arrives with the ACL editor. Left mounted they would offer a feature whose other half does not exist. The two components are complete and reviewed; only their call sites here are commented out, with a note pointing the follow-up PRs at them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BjfMkJyKzodxkobqGZ6Lqb * fix(datatables): honour `-- role: x`, and fix the DuckDB attach test Two review findings, both real. `attach_datatable_parses_name_and_role` never compiled: `parse_attach_datatable` returns `Result<Option<_>>` now and one call site kept a single `unwrap`. Its `?Role=analytics` case also asserted a refusal, contradicting the parser in the same commit, which matches the key case-insensitively. Replaced with the cases that are genuinely malformed, and a positive one for the cased key. `-- role: analytics` fell through to the default role — the silent fallback the strict parser exists to remove, for the spelling most likely to be typed. The keyword now accepts an optional colon, attached or spaced, while a word that merely starts with it (`rolebased`) is still not an attempt. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BjfMkJyKzodxkobqGZ6Lqb * fix(datatables): clone a fork's pointer instead of failing after the copy Forking a fork with cloning left an orphan database. The preflight resolves the pointer and sees the governing entry, so both endpoints ran and filled the new database; `apply_forked_datatable` then refused the inherited pointer and rolled the fork back, stranding a registered `wm_fork_*` that no entry names and whose name blocks the retry. Refusing earlier would have been the smaller change, but forking a fork and cloning worked before pointers existed, so it would trade an orphan for a regression. Resolve what the pointer names and write the terminal entry the clone needs: the whole `database` object rather than a patch of its `resource_path`, since a pointer has none, and `reference` removed with it. Also accepts `-- role=x` and `-- Role = x`, two more spellings that fell through to the default role. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BjfMkJyKzodxkobqGZ6Lqb * fix(datatables): refuse to roll back the catalog while roles exist The down migration dropped the table and left every role behind: live Postgres logins whose passwords only that table carried, so after a revert Windmill could neither use, disable nor delete them, and re-applying could not recreate them because the names were taken. Cleaning up here is not possible either — dropping a role means reassigning what it owns in every instance database, and a migration runs in one — so it now refuses while the catalog is non-empty and says to delete the roles through instance settings, which does the cluster work. Also enforces the instance-only invariant the resolved-pointer clone relies on rather than only asserting it in a comment. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BjfMkJyKzodxkobqGZ6Lqb * refactor(datatables): settle clonability in one place, before anything is created A clone is three stages a workspace apart — `create_pg_database`, then `import_pg_database`, then `apply_forked_datatable` inside the fork transaction. Only the third can roll back, and `CREATE DATABASE` is not transactional, so any refusal that lives there strands a registered `wm_fork_*` that no entry names and whose name blocks the retry. That orphan has now been fixed three times, most recently reintroduced by a guard added one commit ago. Patching each new refusal into the first endpoint is not the fix; having two places that can refuse is. `ensure_datatable_is_clonable` now answers every reason a copy can be refused and returns what it resolved, and the stage that writes the entry only does the work. Also takes an ACCESS EXCLUSIVE lock before the rollback guard counts, so a role created concurrently cannot slip between the check and the drop. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BjfMkJyKzodxkobqGZ6Lqb * fix(datatables): let a retried clone reclaim its own leftover database A clone creates its target database one request before it copies into it, and the fork that would name it is written a request after that. Any failure in between — a pg_dump error, a bad restore, a dropped connection, the source's roles changing mid-flow — left a registered `wm_fork_*` that no entry names, and every retry then failed on its name. This predates data table roles. `create_pg_database` now reclaims such a leftover before creating: only a `wm_fork_*` database Windmill registered as a data table database and that no data table or ducklake entry names, in any workspace, archived ones included. The drop never terminates connections, so a clone still copying into it makes the reclaim fail instead of being cut off. It is limited to callers who administer the source — reaching it is not enough, since on a data table without roles every member reaches it — and anyone else gets the refusal an existing database always got. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Revert "fix(datatables): let a retried clone reclaim its own leftover database" This reverts commit |
||
|
|
4f7f705f8c |
docs: make herdr the documented worktree manager (#10727)
* chore: share worktree port assignment across worktree tooling The slot scan and the .env.local writer existed twice, once inline in scripts/worktree-env and once in scripts/post-create.sh. Both are now wm_assign_ports and wm_write_env_local in scripts/worktree-common.sh, so a third caller cannot drift from the other two. wm_assign_ports takes the worktree root instead of reading the process cwd, which lets it run from outside the worktree, and prints its slot line to stderr so stdout carries only the port pair. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: describe both worktree managers in the dev environment section The section assumed webmux and tmux throughout. Worktree tooling is being trialled on herdr alongside it, so the detection markers and the pane inspection commands now appear side by side. Corrects the pointer for per-worktree values: .env.local is written by both managers through wm_write_env_local and is readable, so it is the portable source. webmux's runtime.env stays documented as the place carrying the extras .env.local lacks. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: point at the herdr plugin setup Naming the repo left the reader to work out what to do with it. The plugins do nothing until they are installed and their actions are bound, and Herdr has no action palette to discover them through. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore: narrow the shared helper to .env.local writing Slot assignment goes back into worktree-env: it had one caller, and the herdr plugin it was extracted for provisions worktrees before the checkout is usable, so it carries its own copy rather than sourcing one whose contents depend on whichever branch the main checkout sits on. Writing .env.local stays shared - worktree-env and post-create.sh each had the same eleven lines. Also corrects AGENTS.md: .env.local is a shared format rather than a shared function, and WM_DB_NAME comes from it rather than webmux's runtime.env. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs: correct the .env.local readability claim The file-read tool denies every .env.* path via .claude/settings.json, .env.local included, so the section points at cat instead of implying the file is readable. DATABASE_URL carries an export prefix, which the bare 'source .env.local' in .envrc needs to reach cargo and sqlx, and which a ^DATABASE_URL= grep would miss. Documented rather than removed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs: target the backend pane in the webmux capture-pane cell Without -t the command dumps the agent's own pane. Matches the form .webmux.yaml already uses in its startup prompt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs: make herdr the documented worktree manager Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs: split how the two managers pick up a CARGO_FEATURES change Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
56a6ad322e | feat: snap-collapse the sidebar by dragging it to the left edge (#11225) | ||
|
|
85b36a2de5 |
feat(ai-chat): disclose an app's exposure on deploy, refuse drag-and-drop apps (#11137)
* feat(ai-chat): disclose an app's exposure on deploy, refuse drag-and-drop apps Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018XFF7zKcuVqzvKDBTMiWHk * refactor(ai-chat): leave the app kind check at deploy to the server Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018XFF7zKcuVqzvKDBTMiWHk * feat(ai-chat): disclose a guest deploy where the instance admits guests Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018XFF7zKcuVqzvKDBTMiWHk * test(ai-chat): pin each guest switch in the deploy disclosure Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018XFF7zKcuVqzvKDBTMiWHk * docs(ai-chat): describe what a staged draft over a low-code app actually breaks Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018XFF7zKcuVqzvKDBTMiWHk * feat(ai-chat): report who may open an app when reading it Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018XFF7zKcuVqzvKDBTMiWHk * fix(ai-chat): report a guest app as inert where no guest is admitted Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018XFF7zKcuVqzvKDBTMiWHk * fix(ai-chat): report who may open an app that has a draft over it Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018XFF7zKcuVqzvKDBTMiWHk * docs(ai-chat): say which mode a drafted app read reports Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018XFF7zKcuVqzvKDBTMiWHk --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
6186a0645d |
feat: edit variables, resources and triggers in their own session tab (#11206)
* feat: edit variables, resources and triggers in their own session tab Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: take a trigger's flow kind from its config and name the written trigger Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor: one operating-workspace context for editors acting on a session's workspace Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: reload page item tabs only in sessions acting on the tool's workspace Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: resolve open in workspace for every preview tab kind in one place Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: guard every component under a session editor against the navigation workspace Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: show a page item tab whose item is gone instead of mounting its editor Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: follow a saved page item in the tab's own workspace and restore in-frame rows Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: close a page item tab from its editor header Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: judge trigger permissions by the user acting in the session workspace Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: judge every session-editor permission by the user acting in that workspace Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep session permissions reactive to the acting role and restore tabs as last seen Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: default Path's owner controls to the user acting in its workspace 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: Ruben Fiszel <ruben@windmill.dev> |
||
|
|
9e0e835c68 |
fix: bump git sync hub scripts to cli 1.815.1 (#11239)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
9994e03bc7 |
feat: add an ACL editor for data table roles (#11063)
* feat(datatables): add an ACL editor for data table roles Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: take every pooled connection before the ACL apply locks Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BRoYE5ZeAVvrDYdfhDAYXb * fix: refresh grant options only after the ACL apply validates its plan Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BRoYE5ZeAVvrDYdfhDAYXb * fix: add only missing grant options before an ACL apply, never default privileges Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BRoYE5ZeAVvrDYdfhDAYXb * fix: run one data table ACL apply at a time per server before it connects Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BRoYE5ZeAVvrDYdfhDAYXb * fix: hold the ACL connection to the database that was authorized Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BRoYE5ZeAVvrDYdfhDAYXb * fix: build the ACL connection from the authorized data table entry Resolving the settings again could land on a resource with the same database name on another server, which the later entry checks never see. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BRoYE5ZeAVvrDYdfhDAYXb * fix: check ACL read reach against the entry it connects from Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BRoYE5ZeAVvrDYdfhDAYXb * chore: update ee-repo-ref to 7e338e4dabf91689bfd7fb0333c6534040b17b59 This commit updates the EE repository reference after PR #787 was merged in windmill-ee-private. Previous ee-repo-ref: 0edd40979cf36bfba59323f3f6a0811ae1369cf5 New ee-repo-ref: 7e338e4dabf91689bfd7fb0333c6534040b17b59 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> |
||
|
|
87761d1797 |
feat: let every EE instance set the announcement banner (#11238)
* feat: let every EE instance set the announcement banner, not only cloud Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: move the announcement banner setting to the bottom of the general tab Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |