mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-24 16:01:42 +00:00
e0510fea21006a06eee7eecd4587161970d7f4d5
7083 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e0510fea21 |
fix: keep every value of a repeated multipart field (#10800)
* fix: keep every value of a repeated multipart field Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore: drop pre-change narration from a multipart test comment Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
dc0df45c81 |
chore: bump git-sync hub scripts to windmill-cli 1.794.0 (#10802)
* test: assert the unpacked repo symlink without following it `unpack_keeps_a_link_that_stays_in_the_repo` read through the link it had just unpacked. Windows stores a symlink's target verbatim and its object manager rejects the `/` in a POSIX one, so `read_to_string` came back with `ERROR_INVALID_NAME` and the release's `cargo_test_windows` job was red. Pin what the function is responsible for on every platform — the link is kept and materialized — and read through it only where a POSIX relative target resolves. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P3WRtxdKNGdomWX9vaAYGx * test: key the cli sync-map fixtures with the platform separator A sync map is keyed with the platform separator on both sides — `FSFSElement` walks the tree with `path.join`, and the remote `ZipFSElement` starts at `"." + SEP` and joins from there — while an `!inline` reference is always forward-slash. `lock_dedup.ts` follows that convention; the fixtures did not, so on Windows they built a map shape the CLI never produces and 12 of them failed. `getTypeStrFromPath` is the same story: it matches `"dependencies" + SEP`, and the test handed it a forward-slashed path. Build the fixture keys through the separator, leaving the `!inline` references and the `present` map forward-slash, as `sync.ts` hands them over. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P3WRtxdKNGdomWX9vaAYGx * ci: skip the discord comment relay when the thread lookup returns none A rate-limited or unauthorized Discord response carries no thread list, and under `bash -e` that aborted the step — jq cannot iterate null, nor parse the HTML error page Cloudflare answers a 429 with — before it reached the "thread not found, skipping" branch right below. Three comment relays failed that way on the 1.794.0 head. Keep the step green for both, but tell them apart: a response with no thread list is a delivery that was dropped for a reason worth seeing, so it warns with the body it got, while a PR that genuinely has no thread stays quiet. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P3WRtxdKNGdomWX9vaAYGx * chore: bump git-sync hub scripts to windmill-cli 1.794.0 --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
5088e13705 |
fix(ci): unbreak the windows test jobs and the discord comment relay (#10799)
* test: assert the unpacked repo symlink without following it `unpack_keeps_a_link_that_stays_in_the_repo` read through the link it had just unpacked. Windows stores a symlink's target verbatim and its object manager rejects the `/` in a POSIX one, so `read_to_string` came back with `ERROR_INVALID_NAME` and the release's `cargo_test_windows` job was red. Pin what the function is responsible for on every platform — the link is kept and materialized — and read through it only where a POSIX relative target resolves. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P3WRtxdKNGdomWX9vaAYGx * test: key the cli sync-map fixtures with the platform separator A sync map is keyed with the platform separator on both sides — `FSFSElement` walks the tree with `path.join`, and the remote `ZipFSElement` starts at `"." + SEP` and joins from there — while an `!inline` reference is always forward-slash. `lock_dedup.ts` follows that convention; the fixtures did not, so on Windows they built a map shape the CLI never produces and 12 of them failed. `getTypeStrFromPath` is the same story: it matches `"dependencies" + SEP`, and the test handed it a forward-slashed path. Build the fixture keys through the separator, leaving the `!inline` references and the `present` map forward-slash, as `sync.ts` hands them over. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P3WRtxdKNGdomWX9vaAYGx * ci: skip the discord comment relay when the thread lookup returns none A rate-limited or unauthorized Discord response carries no thread list, and under `bash -e` that aborted the step — jq cannot iterate null, nor parse the HTML error page Cloudflare answers a 429 with — before it reached the "thread not found, skipping" branch right below. Three comment relays failed that way on the 1.794.0 head. Keep the step green for both, but tell them apart: a response with no thread list is a delivery that was dropped for a reason worth seeing, so it warns with the body it got, while a PR that genuinely has no thread stays quiet. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P3WRtxdKNGdomWX9vaAYGx --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
55b6279058 |
chore(main): release 1.794.0 (#10782)
* chore(main): release 1.794.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> |
||
|
|
9022dc9d44 |
fix: confine job tokens to workspace-scoped API routes (#10631)
* fix: confine job tokens to workspace-scoped API routes Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep the object-storage connection test reachable from a job token Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep the workspace-exists check the CLI makes reachable from a job Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test: reconcile the job-token caps after #10124 The workspace-confinement middleware answers a workspace-less route before the privilege gate behind it runs, so the cases #10124 added on those routes now see 403 rather than 401. Rejection is what they assert, but two of them needed more than a status change: - `list_worker_groups` asserted only that the response body omits the static env value, which an error body satisfies for the wrong reason. It now asserts the status, keeping the secret check as a second assertion. - `require_super_admin` lost its only unshadowed route. `GET /api/w/{workspace}/users/list_addable` is gated solely by that call and names a workspace, so it reaches the gate and pins it at 401. The module doc states the two-layer rule once; the file covers both caps, so it is no longer named for either one alone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs: correct the workspaces/exists rationale and the parquet gate note `workspace` carries no row-level security, so `exists_workspace` running through `user_db` does not filter by membership as the comment claimed. State what the route actually discloses — whether a workspace id is taken. The object-storage case explained why a 404 would satisfy the assertion for the wrong reason, which described the earlier `assert_ne!(403)`; against the 422 it now asserts, a 404 fails. Say instead why the case is gated on the feature. * fix: let a job token keep the workspace-less routes that carry no workspace Confinement refused every route outside the allowlist, including ones that answer purely from the caller's own account or from the request body. Those cross no workspace boundary, so refusing them buys nothing: - `users/email` returns a value already inside the token, and `workspaces/allowed_domain_auto_invite` tests the caller's own address against a static list. Neither opens a transaction. - `users/usage` reads the caller's own row; `users/tutorial_progress` reads and upserts a UI bitfield keyed on the same email. - `schedules/preview` takes no `ApiAuthed` at all — it computes the occurrences of the cron expression in the body and returns nothing the caller did not send. The rule, not the list, is what the doc comment states: answers from the caller's own account, the request body, or content identical for every workspace; never naming another workspace, never instance configuration. The candidates it excludes are written down with their reasons, since `users/list_invites` reads as caller-scoped until you notice the response carries a workspace id per invite. Regression covers both directions — the new entries answer, and the rejected caller-scoped reads stay refused. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test: reach the privilege gates confinement hides `require_devops_role` and `require_instance_admin` gate only workspace-less routes, so confinement answers every request that would reach them: no HTTP case can tell whether they still cap job tokens, and both could lose that check with this suite green. `require_super_admin` has a workspace-scoped route to reach it; these two have none, so call them directly instead. The identity used is the fixture's real superadmin, so the passing half proves the rejection keys off `job_id` rather than off the user. Also separate two claims the write-allowlist doc had merged into one sentence: no entry writes outside the caller's own account, but what each may read differs, and the workspace-existence check answers for any id. * docs: say that the object-storage probe writes The write-allowlist lead claimed no entry writes state outside the caller's own account. `test_s3_bucket` puts an object into the store the body names and deletes it again, so it does write; a failure between the two leaves the object behind. The invariant that holds is about Windmill state. Say so in the lead, and describe the put/delete in the entry itself rather than leaving "acts only on the store the request body describes" to imply a read. * test: cover the last two job-token gates confinement hides Seven guards key on `ApiAuthed::job_id`. Three keep a workspace-scoped route and are exercised over HTTP; the other four are reachable only through workspace-less routes, which confinement now answers first, so nothing observed whether they still cap job tokens. `require_devops_role` and `require_instance_admin` were already called directly. Add the two that were not: `forbid_superadmin_job_token`, and `forbid_elevated_job_token`, whose call sites are `create_token`, `update_token_scopes` and `set_password` — all workspace-less. Both key on two conditions rather than one, so all three combinations are pinned: neither fires without job provenance, and neither fires for an unelevated identity. The second matters — collapsing either into a blanket job-token refusal would stop ordinary users creating tokens, and no other case would catch it. The doc comment records which of the seven each route covers. * docs: correct which job-token gates have no observable route The previous commit put `forbid_elevated_job_token` among the guards reachable only through workspace-less routes, and its message named three call sites. It has six, and two are workspaced: `mint_app_embed_token` and `mint_raw_app_sdk_token`. Its superadmin branch is therefore already exercised over HTTP — the 401 the embed-token case asserts is this gate. So three of the seven lack an observable route, not four. Its direct assertions stay: the embed-token case only ever reaches it with an elevated identity, and the unelevated-negative case is what would catch the gate being collapsed into a blanket job-token refusal. * test: pin is_instance_admin, and stop enumerating gates in prose `is_instance_admin` is `authed.is_admin && authed.job_id.is_none()`, so a census built by searching for `job_id.is_some()` could not see it. Both its call sites are workspace-less, and it returns a bool that selects obfuscation rather than refusing — a job token reading `true` leaks `env_vars_static` instead of being turned away. Pin both directions. The doc comment tried to account for every job-token guard and which route exercised it. It was wrong three times running: the count, the call sites of `forbid_elevated_job_token`, and the claim that the CUSTOM_INSTANCE_DB case covers `is_super_admin_authed` when that path tests `job_id` inline. A table that has to be rederived from six crates to stay true does not belong in a comment, so it now states only why these calls are direct. * docs: name the right is_instance_admin caller The comment credited "the concurrency-group listing" with obfuscating rows. The second caller is `prune_concurrency_group`, which returns PermissionDenied; the obfuscating one is `list_worker_groups`. Keep the claim to that single caller, which is what makes this guard fail by leaking `env_vars_static` rather than by admitting a request. --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
3c8e4b43fd |
fix: resolve a script path to its new version as soon as the lock lands (#10794)
* fix: resolve a script path to its new version as soon as the lock lands Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011H5ygpzQHkPeYsjiP9GzBy * fix: tell MCP script deploy callers to stop polling on a lock error Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011H5ygpzQHkPeYsjiP9GzBy --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
8e508ea01a |
feat: support application default credentials for gcp pub/sub triggers (#10778)
* feat: support application default credentials for gcp pub/sub triggers Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: address review findings on gcp application default credentials Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: address review nits on gcp application default credentials Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: key the gcp credential-mode permission off the loaded mode Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: gate enabling an ADC gcp trigger on workspace admin Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: lock the gcp trigger row while authorizing a mode change Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: skip admin-only gcp listing when the caller cannot use those credentials Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore: update ee-repo-ref to 54bf630681000c8ed87a7067e357118e015123b1 This commit updates the EE repository reference after PR #738 was merged in windmill-ee-private. Previous ee-repo-ref: 91d0e228a0ad226625278b400c64f96a61404a10 New ee-repo-ref: 54bf630681000c8ed87a7067e357118e015123b1 Automated by sync-ee-ref workflow. --------- Co-authored-by: Claude Opus 5 <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> |
||
|
|
92a454b7a8 |
fix: split the MCP script tools into createScript and updateScript (#10783)
* fix: let the MCP createScript tool deploy without a parent hash The tool advertised creating a new script with `parent_hash` left unset, but `parent_hash` was one of its declared arguments — and a client that requires every declared argument to be filled has no way to leave it unset. The values such a caller invents (`""`, `"0"`, a zero hash) are all rejected by `/scripts/create`, so no script was ever created. `parent_hash` is now gone from the tool, and the MCP layer sends `auto_parent` in its place: the server resolves the lineage from the path, creating the script when the path is free and deploying a new version of it when it is not. That is what the tool already claimed to do, and it no longer asks the caller to track a hash to do it. `x-mcp-tool-fixed-fields` is the general mechanism behind this — body fields the MCP layer fills in itself, absent from the tool schema. A null argument is also dropped from the assembled body now, for the same reason the placeholder hashes were a problem: it is how a caller with no value to give says so, and the API rejects it rather than falling back to the field's default. Fixes GIT-973 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: hash a script version once auto_parent has resolved its parent `create_script` hashed the incoming script before the `auto_parent` block filled in `parent_hash`, and the version hash covers that field. A deploy that let the server resolve the parent was therefore hashed as if the path had no history, so redeploying content the path had held before collided with that archived version and returned "A script with same hash ... already exists!" instead of becoming a new version of the lineage. Reverting a script to an earlier state was impossible for any caller relying on auto_parent alone, which is now every MCP caller. The hash and the duplicate-hash check move below the resolution, so an auto_parent deploy hashes the lineage it will actually be attached to. Callers passing an explicit `parent_hash` are unaffected: the resolution block leaves their `ns` untouched, so they hash exactly as before. The CLI masked this by sending `parent_hash` and `auto_parent` together, using auto_parent only as a stale-hash fallback. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs: state the constraint that pins the script hash site Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: reject a fixed-fields spec the MCP layer would not honour `validate_fixed_fields` ran only for an operation that declares a request body, and passed any body whose properties it could not see. Two shapes reached the generated tool with fixed fields that are dropped at call time: an operation with no `requestBody`, where the body builder returns before reading them, and a pass-through body, which carries the runnable's own arguments and never receives a key of ours. Both are now generation-time errors, so the only specs that get the extension are the ones where it means something. Also name the folder-derived `on_behalf_of` alongside `parent_hash` at the hash site: both are written to `ns` before it, and a reader who knows about only one could reintroduce the early hash. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: keep fixed fields internal and catch a misspelled one `EndpointTool` is what `list_tools` publishes as the tool catalogue, so deriving `body_fixed_fields` into it put a field in the caller's view that is by definition not the caller's to set, and that the OpenAPI schema does not declare. It is no longer serialized. The generator also only checked a fixed key against the exposed subset of the body properties, which cannot tell a field deliberately left out of `x-mcp-tool-include-fields` from a misspelling of one. A key the API does not declare is now a generation-time error rather than one serde discards in silence, and the extension must be a non-empty mapping — an empty list previously slipped through the type check on its way to being ignored. Narrow the hash-site comment to the ordering it actually constrains. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat: split the MCP script tools into createScript and updateScript Scripts were the only entity in the MCP surface without the create/update pair every other one has, because the REST API has no update route for them: a script is immutably versioned, so `POST /scripts/create` is also its update, and one tool had to infer which the caller meant from the state of the path. That inference is what GIT-973 is. `parent_hash` told the two apart, and an MCP client that requires every declared argument to be filled has no way to leave it unset, so no script could be created: `""` is a 422, `"0"` is a 422, and `"0000000000000000"` is a 400. Naming the intent removes the field instead of the guard. `createScript` means the path should be free and keeps refusing an occupied one; `updateScript` names the version it supersedes in its URL, so the body carries no hash either. Picking the wrong one now fails loudly rather than succeeding on the wrong script. - New `POST /w/{workspace}/scripts/update/{path}`, deploying a new version of the script the URL names. Its body `path` is the destination, defaulting to the URL's, so setting a different one moves the script and keeps its history — which no MCP client could ask for while `createScript` was the only tool. - New `x-mcp-tool-optional-fields`, dropping a body field from the tool's `required` where the handler defaults it. `updateScript` uses it for that destination path: required, an agent has to restate the path on every edit, and a value that drifts from the URL's silently moves the script. - `assemble_request_body` drops null-valued arguments, matching what the pass-through branch already did. A client that must fill in every argument says "no value" with `null`, and the API rejects that for a bare `String` field rather than falling back to its default. Fixes GIT-973 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CEWFHpmTBauDBi93MnsT7s * fix: confine updateScript to the token's script paths `endpoint_path_policy` is what applies an `mcp:scripts:<pattern>` token's path patterns to an endpoint tool, and a tool it does not name is not confined at all. `updateScript` was not named, so a path-scoped token could deploy over, and move, any script in the workspace: the proxy mints a bare `scripts:write` for a caller whose only scopes are `mcp:`-prefixed, and nothing downstream held a pattern. The destination path has to bind only when supplied — omitting it is how a caller updates in place — so `PathArgs` grows `optional_fields`, checked when present and never required. Empty reads as absent, matching the handler, which now takes an empty body `path` for "leave it where it is" rather than moving the script to the empty path: a caller obliged to fill in every field sends `""` as readily as null. That shape also fixes `updateFlow`, whose entry named `path__path` for the URL argument. The generator gives the URL path the plain name, so the lookup never matched and every confined call failed closed on a missing argument. Both sides now have a drift guard: a script/flow tool the URL addresses by path must have a policy. The backend one lives in windmill-api, where the generated catalogue is, since the policy is in windmill-mcp and neither crate sees both. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CEWFHpmTBauDBi93MnsT7s * fix: address the review round on the script tool split Four findings, three of them one bug: a destination path the caller left empty. `update_script` read it as "leave it where it is", the confinement check skipped it on the strength of that, and `update_flow` did neither — it takes the empty string literally and moves the flow there, so the skipped check was the only thing standing in front of that move. A database constraint refuses the empty path, so nothing was reachable through it, but the confinement was relying on a property of one handler that its sibling did not have. The MCP layer now strips an empty optional destination from the arguments, so no handler receives one and there is nothing left for the check to skip. Neither tool depends on the other's reading of it any more. `update_script` also resolved the head before opening the deploying transaction. A version landing in between is caught — it leaves a child behind, and the linear-lineage check refuses that — but an archive leaves none, and the hash of an archived version still exists, so the deploy would have chained onto it and revived the script the archive had just retired. The resolution moves into the transaction. The scope check on the URL path moves ahead of that resolution, so a path outside the token's scope answers the same whether or not a script is there, rather than telling the two apart through 404 against 403. `x-mcp-tool-optional-fields` goes: the generator already strips a body field that collides with a same-named path parameter from `required`, so the extension regenerated byte-for-byte identical output. The test that pinned the destination as optional stays — it pins the behavior, which is now the collision handling's. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CEWFHpmTBauDBi93MnsT7s * fix: lock the head an updateScript supersedes Moving the resolution into the deploying transaction narrowed the archive race without closing it. The plain SELECT took no row lock, so an archive could still land between it and the parent-existence check below, which finds the parent by hash and never looks at `archived` — the deploy then chained onto the archived version and inserted a live child, reviving the script the archive had retired. `FOR UPDATE` on the resolution is what makes the row the head rather than a head it once was: the archive either waits for the deploy, or wins and leaves the row failing the `archived` qualifier on re-check, so no version resolves at all. The regression test stages that interleaving rather than approximating it. It holds the head row from a second connection so the deploy parks on it, waits for a backend to actually be blocked before archiving — without that wait the request loses to a local UPDATE and never reaches its resolution, which is the sequential case the neighbouring test already covers — then asserts the update is refused. It returns 201 and revives the script with the lock removed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CEWFHpmTBauDBi93MnsT7s * fix: have the MCP layer name the path an update keeps The tool lets a caller omit the destination, and the endpoint was absorbing that by accepting a body without a `path` and defaulting it from the URL. The OpenAPI schema says `path` is required, so the two disagreed and a generated REST client could not follow the contract the description promised. The MCP layer fills the destination in instead, from the path the item is already at, since that is what omitting it means. The endpoint then always receives a body naming its own path and matches its schema, `update_script` takes a `NewScript` rather than picking a JSON object apart to inject a default, and the empty string stops being a value any handler has to interpret — `update_flow` reads one as the empty path, which is why it was stripped a commit ago. The alternative, an `EditScript` schema differing from `NewScript` only in whether `path` is required, was measured and rejected: openapi-ts drops the `required` of an `allOf` branch, so `NewScript` came out with every field optional and broke 15 frontend types. Loosening a schema every API consumer shares, to make one field optional on one route, is the worse trade. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CEWFHpmTBauDBi93MnsT7s * fix: tell a superseded update apart from a missing script Locking the head made the loser of two concurrent deploys answer 404 "Script not found" for a path the caller can see holds a script: its lock re-check finds the row archived and filtered, and nothing looked further. It now looks — a live version at the path means this deploy lost to one that superseded the version it set out to supersede, which is a conflict to retry, not a script to go find. The regression test stages that interleaving the way the archive one does, with the winner leaving a live head behind rather than an archived path. It answers 404 with the branch removed. The rationale for the lock also sat in two places; it stays at the query, which is where dropping it would do the damage. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CEWFHpmTBauDBi93MnsT7s * docs: drop the path default update_script no longer applies The handler stopped defaulting the body's path when the MCP layer took the job over; its doc comment still described the old contract. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CEWFHpmTBauDBi93MnsT7s * docs: sync the deref YAML with the update route's path contract The dereferenced bundle rewraps prose at its own width, so the edit that updated the canonical spec and the JSON bundle matched nothing here and left the served YAML still offering a default the endpoint no longer applies. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CEWFHpmTBauDBi93MnsT7s * docs: stop the script tools describing a parent_hash they cannot take `description` is read by two audiences: it documents the route, and it opens the MCP tool's text. Written for the first, it told an agent that createScript "does it too when given that version's `parent_hash`" — a field neither tool exposes, and inviting exactly the call this branch exists to make impossible. updateScript's told the agent to repeat the URL's path while its own instructions say to omit it; both work, since the MCP layer fills it in, but only one of them can be the advice. Both now describe what the operation does and leave the mechanics to the text that belongs to each caller: the request body's own description for REST, the tool instructions for an agent. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CEWFHpmTBauDBi93MnsT7s * docs: give the create route's two audiences their own description Removing the `parent_hash` sentence took a true fact out of the REST documentation: the create route does still deploy a new version, and still rename, when the body names the version it supersedes. Nothing replaced the explanation, and the field carried no description of its own. `description` cannot serve both readers — it documents an endpoint whose schema has `parent_hash`, and it opens a tool whose filtered schema deliberately does not. `x-mcp-tool-description` stands in for it on the tool, the way `x-mcp-tool-name` already does for the name, so the route keeps its full contract and the agent is not told to send a field it has no way to send. What `parent_hash` does now sits on the field, where a REST caller looks for it and where `x-mcp-tool-include-fields` drops it before an agent sees it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CEWFHpmTBauDBi93MnsT7s * docs: tell an agent a new version is not runnable the instant it deploys A deploy returns before its lockfile exists, so a script run straight after one can still execute the previous version. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CEWFHpmTBauDBi93MnsT7s * docs: say why a new version is not runnable the instant it deploys Its lock is generated asynchronously, so a script run straight after a deploy can still execute the previous version. On both script tools: a freshly created script is no more immediately runnable than a freshly updated one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CEWFHpmTBauDBi93MnsT7s * docs: bound the wait after a deploy instead of naming a signal for it `getScriptByPath` reports the new hash the instant the version exists, while its lock is still null, so the previous version is what a run by path executes. There is no signal that fixes this: the deploy evicts DEPLOYED_SCRIPT_HASH_CACHE, but anything resolving the path before the lock lands re-populates it with the old hash, and the lock landing evicts nothing. Waiting for a non-null lock is necessary and not sufficient, so pointing at one would have been a second wrong answer. Measured: a run right after the lock lands still gets the previous version, and the same run 65s later gets the new one, which is the cache's 60s TTL. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CEWFHpmTBauDBi93MnsT7s --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Ruben Fiszel <ruben@windmill.dev> |
||
|
|
d85050f505 |
feat: upgrade bun to 1.4.0 and demote deno in the language picker (#10784)
* chore: upgrade bun to 1.4.0 in dockerfiles and CI pins * chore: move deno last in the language picker and relabel it Deno * chore: move deno last in the pipeline language picker too * chore: pin debugger image to bun 1.4.0 and trim the deno picker comment * chore: state the deno picker constraint without referencing the old order * fix: stamp bun lockfiles back to v1 while the fleet predates bun 1.4 * fix: ask bun for a v1 lockfile instead of rewriting one, and refuse an escalated lock * chore: warn instead of silently storing a lockfile with no readable version |
||
|
|
2439a610be |
chore(main): release 1.793.0 (#10764)
* chore(main): release 1.793.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> |
||
|
|
e866b68cdf |
feat: surface execution usage in the sidebar and explain what an execution is (#10760)
* feat: surface execution usage in the sidebar and explain what an execution is Users read "executions" as a job count and are surprised by the real number, which meters a second of compute. Every place the UI prints an execution count now says so, and the sidebar carries a usage meter for the quota that will bind first. Adds SidebarUsage at the bottom of both sidebar surfaces: a ring in the collapsed rail, a labelled bar when expanded, and a modal breaking down every quota. On the free tier it meters the per-user and per-workspace 1000-execution caps; on a paid plan it meters workspace usage against the executions the workspace's seats already include. Item.tooltip was inert on disabled dropdown rows: DropdownSubmenuItem rendered the info icon inside the disabled button, which swallows hover, and the row's own title attribute shadowed any wrapper title. Both renderers now fall back to a wrapper title the way DropdownV2Inner already intended. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep the usage meter tied to the workspace it describes isPremiumStore held the previous workspace's tier across a switch, which no consumer noticed while it only gated affordances — the usage meter is the first surface to render a number from it, and would have shown a paid seat quota for a free workspace. It is now undefined until the active workspace's tier is known, and a superseded response no longer writes. The seat fetch had the same shape: a slow response for the workspace we left overwrote the current count and stayed wrong until the next switch. The usage wrapper also carried the padding the brand-mark row used to own, which shifted the sidebar bottom by 4px on every instance where the meter renders nothing. The component owns its own padding instead. Names the collapsed ring for assistive tech, which otherwise saw an unlabelled button whose only signal was the arc's color. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: scope the usage meter to the billing workspace and a known tier A fork's usage, tier and bill all resolve to its billing root, but its member list is deliberately a subset of the root's, so counting fork members metered root usage against a fork-sized cap and invented billed-seat overages. Seats now come from the billing root, and the paid meter stays hidden when that root is not visible from the fork. The tier was cleared only after the user-store round-trip, so the meter rendered the previous workspace's tier for the length of it — a free→paid switch showed the 1000-execution hard cap on a paid workspace, not a race but every time. The clear now happens before the first await. Workspace usage had neither guard: a superseded response overwrote the store permanently, and the meter is the first surface to print that number as its headline rather than bury it in a dropdown. The free-tier counters keyed off `!$isPremiumStore`, which reads an unknown tier as free and flashed the free-tier blocks during a paid-to-paid switch. They wait for a known tier instead. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: never render an unresolved execution count as zero The workspace-usage clear wrote 0, which is a real usage value: an in-flight or failed fetch rendered as a green "0/1,000" bar, and a rejection left it there for the session because loadUsage had no failure path. Usage is now undefined until it resolves, each endpoint is assigned on its own so one failing leaves the other's number intact, and a quota is listed only once its own usage, tier and cap are known. The legacy counters show an em dash rather than a fabricated 0. The fork gates read an unknown tier as not-premium, so clearing the tier on switch made the fork entry point disappear for the length of the fetch on a paid-to-paid switch. They hold while the tier is unknown. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: read the usage endpoints as numbers, and fall back to the free tier Both usage endpoints serve text/plain, so the client hands back a string despite the generated `number` type. Interpolation and arithmetic coerced it, which is why nothing noticed before, but `toLocaleString` on a string returns it unchanged — a five-figure count rendered without its thousands separator against a formatted cap. A failed tier fetch left the tier unknown for the session, and consumers hold premium-only affordances through the unknown window so a free workspace kept offering them. It falls back to the free tier instead. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep an unknown tier unknown, and refresh the seat cap on demand Falling back to the free tier on a failed tier fetch fixed the affordance gates by lying to the meter: a paid workspace's real five-figure usage rendered against the 1000 hard cap, red, under "jobs stop running for the rest of the month". The tier stays unknown instead, and the two consumers get what each needs — the meter hides, while affordances read `maybePremium`, which holds through the pending window but fails closed once the fetch has failed. Membership changes elsewhere don't reach this component, so the seat cap could show an overage against a cap that had since grown. It re-resolves when the modal opens, which is when the number is read rather than glanced at. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: let anything showing executions re-read them The counters were written in one place, the root layout, on a workspace change only — so a tab left open all day showed the count from whenever the workspace was opened, and the modal-open refresh could only reach the seat cap, leaving a freshly computed denominator over a stale numerator. Moves the fetch to lib/usage.ts, next to the stores it writes, so the meter can refresh both numbers when its modal opens. Seats follow a membership signal that WorkspaceUserSettings bumps where it already refetches after every mutation, so the cap stops lagging a role change without either side owning the other. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: order concurrent usage and seat refreshes The workspace id doesn't order two requests for the same workspace, and both refreshes can now have two in flight: usage through A→B→A or a modal-open refresh landing on one already running, seats through a membership bump arriving mid-request. An older response could win and restore the count it replaced. Each refresh takes a generation and only writes if it is still the newest. The membership signal also fired on a plain read, so opening the users tab made every consumer re-fetch a list identical to the one it held. It bumps on an observed change to the member set instead, never on the first read. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: count only billable seats, and order the tier requests The cap counted every member row, while the backend bills `NOT disabled AND NOT is_service_account` — a workspace with service accounts got an inflated included quota, which hides a real overage rather than inventing one. The seat basis matches `count_paid_seats` now, and the membership signature carries both fields so enabling or disabling a member re-resolves the cap. The tier fetch was the one refresh still ordered by workspace id alone, so a late failure for a workspace could raise the failure flag over a tier a newer request had already resolved. It takes a generation like the other two. The membership signature is keyed by workspace: this page survives a workspace switch, and comparing one workspace's members against another's reported a membership change where only the workspace had changed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: compare the member set only against the same workspace's Qualifying the signature with the workspace put the workspace inside the value being compared, so a switch made every comparison unequal and bumped the version unconditionally — the opposite of the intent, and worse than before the key. The workspace is the key now, not part of the payload: a different one has nothing to compare against and re-baselines silently. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor: hold the usage and tier fetches in resources Every one of these values belongs to a workspace but lived in a bare store, so each writer and reader re-derived "does this still describe what I'm rendering?" by hand. Nine sites did, and the ones that forgot were most of this branch's review findings: three stale-workspace overwrites, three A→B→A races, and two placeholders (`0` executions, `false` tier) that read as real data because an in-band value was standing in for "not known". `resource` from runed — which frontend/AGENTS.md prescribes for async data, and which ~80 files here already use — supplies all three properties as behaviour rather than convention: a superseded fetch is discarded, the value resets when its key changes, and loading and error are states instead of magic values. The seat count keys on the billing root and the membership version, so both a workspace switch and an added member re-resolve it. That removes three generation counters, two workspace trackers, and the manual clear-and-compare around each fetch. What remains is one publish site that asserts the value still carries the active workspace before it reaches a store. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: order the resource responses that runed does not The refactor claimed `resource` discards a superseded fetch. It does not: its only ordering is an AbortController whose signal the generated client cannot consume, and `current = result` runs unconditionally once a fetch resolves. So a late answer for a workspace we had left still landed in `current`, and the publish site — which trusted `current` — cleared the value on screen for the workspace we were on. That reinstated the races the generation counters had covered. `loading` was standing in for the missing ordering, and it cannot: it is also true during a `refetch()`, when `current` is still the right value. Gating on it meant every re-read blanked the meter, and clicking it unmounted the modal that same click had opened, since both sit behind the quota it had just cleared. Values now carry the scope they describe and `scopedValue` keeps the newest one matching the active scope, so a superseded answer neither publishes nor erases, and a re-read leaves the display alone. The account-wide user counter keys on the account, so a workspace switch no longer clears it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: order responses within a scope, not just across scopes The tag carried what a value described but not when it was asked for, so two fetches for one scope — a refetch landing on an in-flight load, or a second membership invalidation — were indistinguishable and the older won if it landed last. That left the seat cap reading the pre-change number until the next bump or switch, which is the stale cap the generation counters had covered. Widening the tag to the resource key would have fixed it by blanking the bar on every membership change, so the issue order travels alongside the scope instead: `tagged` stamps each request as it is issued, and only a strictly newer answer for the current scope replaces the held one. The unit tests now cover the same-key case they missed; both new ones fail against the key-only guard. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: ignore a user list a newer read has overtaken `lastSeen` was written unconditionally after the await, so a response for a workspace already left overwrote the baseline for the workspace on screen. The next real membership change there then compared against a baseline that was never taken for it, re-baselined silently, and never bumped `workspaceMembershipVersion` — leaving the sidebar on the old seat cap. The `users` assignment had the same hole: an overtaken list could paint over a newer one. Both now go through a single check: a read whose issue order is behind the last applied one is dropped before it touches either. Also trims the two `scopedValue` docstrings and the membership rationale to the four lines AGENTS.md allows, and records there that a failed refresh keeps the last successful value rather than blanking. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: do not claim a plan before the tier resolves Widening `isPremiumStore` to `boolean | undefined` left `UserMenu`'s `{:else}` catching the unresolved state: with the tier still in flight, or after the request failed, a free workspace was told it was on the "Premium plan". Both branches under that block assert a plan, so the block now renders only once the tier is known — which also keeps the bordered divider from appearing empty while it resolves. Verified against the running instance with the tier stubbed slow: unresolved shows neither branch, `false` shows the free counters, `true` shows "Premium plan". Reverting the guard reproduces the wrong label at 300ms. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test: pin how a late answer orders against the read that replaced it Returning to a scope whose earlier read is still in flight is the one case the guard resolves by scope rather than by sequence, and the suite only covered it with nothing outstanding. It now covers the late answer itself: it stands while it is the only value describing the scope, the read issued on returning supersedes it, and it cannot come back afterwards. Also gives the meter the explicit `type="button"` the sibling sidebar rows use. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: size the modal's plan button with unifiedSize `size` is deprecated on `Button`. `unifiedSize="sm"` renders the plan button at the same height and weight as the modal's own Close button. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: match the plan button to the modal's own action button `unifiedSize="sm"` is `h-7`, and the Cancel button `Modal` renders beside it is `px-3 py-[7px]`, i.e. 32px — so the two sat 4px apart. `md` is the unified size that lands on 32px, which pairs them without putting a deprecated prop back. Measured both boxes rather than the new one alone: 32px and 32px, same top. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: instrument the execution meter, and bill-align PremiumInfo's seats The meter's only interaction is opening the modal, so that is what it counts: `usage_meter/opened`, keyed by the plan tier and the quota that was tightest — `free:user`, `free:workspace`, `paid:workspace`. The full set is a type next to the call site so the vocabulary stays readable in one place. The pair is registered in `FEATURE_USAGE_KINDS` (windmill-ee-private), without which the post is dropped with a 204 and records nothing. Verified both halves: the browser posts `{"feature":"usage_meter","kind":"opened","key":"free:user","value":1}`, the running EE image drops it because its registry predates the entry, and `is_recordable_event` accepts it once the entry is there. `PremiumInfo` computed its seats from an unfiltered user list, so the billing page counted disabled members and service accounts that `count_paid_seats` does not bill. Same filter as the sidebar's cap now. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore: point ee-repo-ref at the usage_meter registration Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: read the member list before the seat rows that depend on it `loadPremiumInfo` reads `users` after its own await and nothing recomputes the seat rows when the list lands, so whenever `premium_info` won the race the page rendered zero developers, zero operators and zero seats and kept them. The list is now fetched first, and a failure to read it no longer costs the rest of the page. Also refreshes the registered-action inventory in `docs/feature-telemetry.md`, which the new pair makes 21 across nine features. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: scope the seat comment to the counter it matches The comment claimed parity with the seats actually charged, which nothing in this repo computes: `count_paid_seats` documents itself as counting provisioned members rather than billing's active-user population, and the Stripe quantity is not derived here. What the filter buys is agreement with that counter. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore: update ee-repo-ref to c6902ec2c51dc0ce30962afbfab3e456c5d9b831 This commit updates the EE repository reference after PR #735 was merged in windmill-ee-private. Previous ee-repo-ref: bbc48fae6b73b6d72fe2e125e6003794a4ece167 New ee-repo-ref: c6902ec2c51dc0ce30962afbfab3e456c5d9b831 Automated by sync-ee-ref workflow. --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com> Co-authored-by: Ruben Fiszel <ruben@windmill.dev> |
||
|
|
5099f405d4 |
feat: make the Git Repo Viewer work with GitHub App repositories (#10765)
* fix: resolve the head commit of GitHub App repos in the git repo viewer `get_git_commit_hash` ran `git ls-remote` against the raw resource URL. A GitHub-App-backed repository stores a tokenless URL, so the probe failed with "could not read Username" and the viewer never got past its first step. Resolve the head over the GitHub REST API with a server-side installation token instead, reusing the lookup the auto-pull poller already uses for app repos. Non-app repositories keep the ls-remote path. Also picks up the EE-side allowlist fix that lets the clone hub script request an installation token. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore: update ee-repo-ref to 63c67e2a2db198af26a0334f5be14af7d9987eb1 This commit updates the EE repository reference after PR #732 was merged in windmill-ee-private. Previous ee-repo-ref: 2a260961fa0a9bb5631c17e2f718cb8efb4f9aa2 New ee-repo-ref: 63c67e2a2db198af26a0334f5be14af7d9987eb1 Automated by sync-ee-ref workflow. * fix: honour the app-repo head lookup's not-app-backed result `get_app_repo_head_for_autopull` documents `Ok(None)` as "this repo is not app-backed, use the ls-remote path", which is what the other two callers do. Fall through to `ls-remote` on `None` instead of turning it into a 500, and drop the handler's own `is_github_app` read now that the callee's answer is honoured. Also bumps ee-repo-ref to pick up route-safe ref handling in that lookup. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: serve GitHub App repositories as an archive instead of a token The viewer's clone script asked the server for an installation token and put it in the clone URL. That token is installation-wide and carries the App's full permissions, so minting one requires a workspace admin, and the viewer was therefore admin-only for app-backed repositories. The server now streams a tarball of the commit instead, authorized by read access to the git_repository resource, so no GitHub credential reaches the job. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat: run delegate_to_git_repo playbooks from GitHub App repositories An Ansible job's runnable_path is the user's own script, which no entry in the git-sync script allowlist can match, so `delegate_to_git_repo` could never obtain a token for an app-backed repo. It also gave up entirely on agent workers, whose connection has no database to mint one from. A playbook run only reads a working tree: the clone is followed by one rev-parse for a log line, and nothing after that touches git. So take the same archive route the viewer uses, extracting the commit's tarball into the job's repository directory. No GitHub credential reaches the worker, and agent workers work because the route is HTTP. Archive entries are joined onto the target by hand so a crafted archive cannot write outside the job directory. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: drop the now-immutable secret_url binding Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat: point the repo viewer at the archive-based clone script hub/28905 reads app-backed repositories through the server's archive route instead of minting an installation token, which the backend in this release no longer grants it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: stream repository archives to disk rather than into memory The archive download went through `AuthedClient::get`, whose client caps a request at 20 seconds and whose response was then buffered whole. A repository is arbitrarily large, so that cut off slow downloads and put every job on the worker at risk of running the process out of memory. Add `get_streaming`, the read counterpart to the streaming upload path, and write the response out chunk by chunk. Extraction now creates each entry's parent directory: a tar carries directory entries only by convention, and the traversal guard now has tests, one of which caught the missing parent. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: require admin to read an app-backed repository A `git_repository` resource names the repository rather than holding a credential for it, so read access to one authorizes nothing: anyone who can write a resource path can point one at any repository the GitHub App installation reaches, then read their own resource. The head lookup now requires admin for app-backed repos, matching the archive route and the repository picker, which already limits itself to workspaces where the caller is an admin. Repos that aren't app-backed are untouched and stay open to any reader. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs: describe the repo viewer's hub script as it stands The file read as a patch waiting to be applied, against a hub version two releases stale. Describe what the published script does, including the archive route app-backed repositories now take. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: run the archive fetch under the job poller, off the job directory Three defects in the delegate path's fetch: The download and extraction ran outside the job poller that the git clone paths go through, so a cancelled or timed-out run kept streaming and extracting an arbitrarily large repository while holding the worker. There is no wall-clock bound on the download itself, by design, which is exactly why it needs the poller. The archive was written to a fixed name inside the job directory, where `create_file_resources` has already laid down the run's own files at paths the playbook chooses. A run naming a file `repo_archive.tar.gz` had it truncated and then deleted. It goes to a per-job temp path now. Link entries were unpacked with their target unchecked. `Entry::unpack` writes the link verbatim, so a link out of the tree plus a later entry descending through it writes wherever it points. Targets now face the same containment check as entry paths. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: keep repo symlinks, refuse only writes that go through them The link check rejected any target containing `..`, which is ordinary in a repository — `docs/x -> ../README.md` resolves inside the tree, and a git checkout keeps it. Rejecting it failed the whole extraction for repositories the clone path handles, and app-backed repos have no clone path to fall back to. Targets are preserved as git preserves them. What would let one escape is a later entry written at or underneath the link, so that is what is refused. Extraction also polls an abort flag now: a `spawn_blocking` task outlives the join handle its caller drops, so a cancelled job left it unpacking in the background. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: refuse hard links in a repository archive Leaving link targets verbatim is right for symlinks — git checks them out that way, and an escape needs a second entry descending through the link, which is refused. A hard link is not like that: unpacking one creates it against a target resolved there and then, so an escaping target is useful on its own. No git tree can express a hard link, so an archive carrying one did not come from a repository. Refuse it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore: update ee-repo-ref to 21f79bbbd39ae89665d1a89738630978616aa309 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore: update ee-repo-ref to 37695a769b25d16b34107eedc1076793a8b388c8 This commit updates the EE repository reference after PR #737 was merged in windmill-ee-private. Previous ee-repo-ref: 21f79bbbd39ae89665d1a89738630978616aa309 New ee-repo-ref: 37695a769b25d16b34107eedc1076793a8b388c8 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> |
||
|
|
dad8fed647 |
fix: refuse an MCP endpoint call whose required request body is empty (#10771)
* fix: refuse an MCP endpoint call whose required request body is empty Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs: state the required-body rationale once Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
1f59841a67 |
feat: add WM_ROOT_WORKSPACE, the closest dev or prod workspace of a job (#10776)
* feat: add WM_ROOT_WORKSPACE, the closest dev or prod workspace of a job Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HBMJwogo6jJ1P55uvB3YpF * fix: do not cache a failed root-workspace lookup, and sweep on fork create Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HBMJwogo6jJ1P55uvB3YpF * fix: shorten the agent-worker root-workspace TTL and pin the sweep wiring Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HBMJwogo6jJ1P55uvB3YpF * chore: update ee-repo-ref to a2fa58e5301d3865dd06ad73519e20ba7a5af0f0 This commit updates the EE repository reference after PR #736 was merged in windmill-ee-private. Previous ee-repo-ref: 07a9d26a79a403ae27c48abd508a6699f2c87c49 New ee-repo-ref: a2fa58e5301d3865dd06ad73519e20ba7a5af0f0 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> |
||
|
|
de90e44650 |
docs: document DATABASE_URL_FILE in the env var help (#10773)
Claude-Session: https://claude.ai/code/session_0125f1Vwj7pR9oY8NCxLXwtW Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
f6645af77e |
fix: explain the 6-field cron format when a schedule is rejected (#10768)
* fix: explain the 6-field cron format when a schedule is rejected Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019mUJd8ZRXkzbhmHkZryYoE * fix: phrase the cron hint as a prepend, not an equivalent schedule Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019mUJd8ZRXkzbhmHkZryYoE * fix: withhold the cron example where v1 shifts the weekday Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019mUJd8ZRXkzbhmHkZryYoE * fix: withhold the cron example for any restricted weekday on v1 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019mUJd8ZRXkzbhmHkZryYoE --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
c2deea13b7 |
fix(security): a WM_TOKEN job token can never be a global superadmin (GHSA-hfh4-cx4h-3fcr) (#10124)
* fix(security): a WM_TOKEN job token can never be a global superadmin (GHSA-hfh4-cx4h-3fcr)
Privilege escalation: an app/flow/schedule/trigger execution policy's `on_behalf_of`
(which a `wm_deployers` member can set) could point at a superadmin email. The
resulting job `WM_TOKEN` then passed the email-based superadmin checks, granting
instance superadmin. `forbid_superadmin_job_token` only guarded ~15 of ~75 routes.
Fix at the token layer: a WM_TOKEN must never satisfy a superadmin gate,
regardless of whose email it runs as (sentinel OR a real superadmin).
- `ApiAuthed` gains a `job_id` field, stamped once in `AuthCache::get_opt_job_authed`
from the resolved token's job_id (correct even on cache hits).
- `require_super_admin(db, email)` -> `require_super_admin(db, &ApiAuthed)`, rejects
`authed.job_id.is_some()`. `require_super_admin_email` kept for the few internal
callers without an ApiAuthed.
- `is_super_admin_authed(db, &ApiAuthed)` for the boolean `is_super_admin_email`
authorization branches on request handlers (workspace deletion, fork drops,
dev-workspace attach/archive, object-storage SSRF exemption, custom dbname, EE GHES
+ connected repositories, ...). Migrate ~75 sites (OSS + EE).
- CUSTOM_INSTANCE_DB reads the *authenticated* job_id, not the caller-supplied
`?job_id` query param. Worker-tag check takes a precomputed job-aware `is_super_admin`
on the request path.
Execution-time on-behalf checks (scheduled/flow worker-tag, Cloud enqueue quota,
is_devops_email) are hardened in a follow-up — see
docs/followup-onbehalf-execution-privilege-hardening.md.
Regression tests: a superadmin-email WM_TOKEN is rejected on `require_super_admin`
routes, on `DELETE /workspaces/delete/{w}` (403, workspace preserved), and on the
CUSTOM_INSTANCE_DB lookup with no `?job_id` (401); real superadmin tokens still succeed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: cap devops role at workspace admin and reject reserved on_behalf_of identities
Extends the job-token cap with three pieces:
- `require_devops_role` takes `&ApiAuthed` and rejects job tokens.
`is_devops_email` is true for superadmin emails, so every worker-management,
instance-config and service-log route was reachable by the same superadmin
`WM_TOKEN` that `require_super_admin` already rejects.
- A `job_id` claim that does not parse as a uuid rejects the token rather than
resolving to `None`, which would clear the job provenance and uncap it. Applies
to the internal JWT and the external `jwt_ext_` path.
- Defense in depth at store time: `validate_on_behalf_of` refuses the reserved
internal sentinels as an `on_behalf_of` on apps/flows/scripts/schedules/triggers,
and app execution refuses a policy carrying one — covering already-persisted and
forked-app rows that predate the cap. Deploying on behalf of a real user,
including a real superadmin, stays allowed; the cap handles that at execution.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(mcp): preserve job-token provenance when minting the proxy JWT
The MCP endpoint-tool proxy re-mints a JWT from the caller's ApiAuthed to
forward the proxied request, but passed job_id: None. A job's WM_TOKEN is
capped at workspace admin (GHSA-hfh4-cx4h-3fcr); dropping the job_id here
re-minted an uncapped token that satisfies require_super_admin /
require_devops_role on the proxied route (e.g. listWorkers exposing worker
IPs, job/workspace IDs, and sensitive tags).
Carry api_authed.job_id into create_jwt_token. Adds an in-module regression
that decodes the forwarded JWT and asserts the job_id is preserved for a job
caller and absent for a non-job caller.
Reported by Codex CI review (P1) on #10124.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: cap the admin-or-devops gate at workspace admin for job tokens
require_admin_or_devops (the EE critical-alerts endpoints) grants when the
caller is a workspace admin OR an instance devops. is_devops_email is true
for superadmins, so a WM_TOKEN running on-behalf of a superadmin who is not a
member of the target workspace could clear the devops branch and read/ack that
workspace's critical alerts (GHSA-hfh4-cx4h-3fcr). This gate takes a bare
email, not an ApiAuthed, so the token-layer cap could not see it.
Thread the caller's job-token provenance and reject the devops branch for job
tokens, matching require_devops_role. The workspace-admin branch stays allowed
— that is the cap ceiling. Adds an enterprise-gated regression proving the
bypass is closed and a real superadmin token still clears the gate.
Found while auditing the PR for bare-email gates the choke-point cap misses.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: cap instance-global is_admin gates at workspace admin for job tokens
Three instance-global routes gate on the caller's own `is_admin` claim, which
`ApiAuthed.is_admin` carries into a WM_TOKEN (it is a workspace-admin claim,
true for superadmins too). A job token is capped at workspace admin
(GHSA-hfh4-cx4h-3fcr), so its is_admin claim must not authorize instance
actions on a route with no workspace binding:
- `unarchive_workspace` — unarchive an arbitrary workspace by id
- `prune_concurrency_group` — delete a global concurrency group
- `list_worker_groups` — return unobfuscated `env_vars_static` (may hold secrets)
Add job-token-aware `is_instance_admin` / `require_instance_admin` helpers (the
same shape as `require_super_admin` / `require_devops_role`) and use them at
these three sites. Workspace-scoped `require_admin(authed.is_admin, ...)` gates
are intentionally left unchanged — a workspace-admin job token is within the
cap there. Regression added covering all three; verified it lets a WM_TOKEN
unarchive/leak without the fix and is blocked with it.
Reported by Codex CI review (P1) on #10124.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(mcp): drop orphaned path_field_renames from EndpointTool test helper
The merge with main adopted main's mcp path-substitution refactor (#10162),
which removed the `path_field_renames` field from `EndpointTool` and its
consumer (`substitute_path_params` no longer takes per-field path renames).
main's `runner.rs` `ep` test helper still constructed the struct with
`path_field_renames: None`, so the workspace test build (cargo test --all,
which compiles windmill-mcp's own #[cfg(test)] module under the `server`
feature) failed with E0560. A plain `cargo check` does not compile that test
module, so it only surfaced in CI's cargo_test.
Remove the orphaned field to match the struct.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test: describe the sentinel-rejection policy the forged-identity test asserts
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: complete ApiAuthed initializers in feature-gated tests after merge
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: stop job tokens minting credentials that shed their provenance
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: cap the MCP OAuth approval mint at the same elevated-job-token gate
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: cap the self-service password reset at the elevated-job-token gate
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: cap app embed/SDK mints and scope widening at the elevated-job-token gate
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: keep job tokens from destroying the account they run on behalf of
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: deny job tokens a foreign-workspace admin claim and workspace ejection
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: keep the follow-up inventory in the PR instead of the repo
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: make the session workspace status gate job-token aware
session_workspace_status derived its superadmin branch from a bare email
check, so a job token carrying a superadmin identity resolved the existence
of workspaces it has no relationship with rather than seeing them as
deleted. Switch to is_super_admin_authed, matching every other instance
gate reached from a request ApiAuthed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* revert: leave the global concurrency-group listing on the plain admin gate
The listing exposes concurrency keys across workspaces, which is metadata
rather than a capability, and it 401s rather than degrading. Keep the guard
on the prune route next to it, which is the destructive one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: keep the instance-admin gate on the global concurrency listing
The listing spans every workspace's concurrency keys, and the gate rejects
only job tokens: the !is_admin branch is the pre-existing check, so
workspaced tokens and interactive admins are unaffected.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to d30af67d38954f9012f7bad08da23e347344b4c6
This commit updates the EE repository reference after PR #664 was merged in windmill-ee-private.
Previous ee-repo-ref: 7870573dbc3360f99bada143f094c67dce0d9e9c
New ee-repo-ref: d30af67d38954f9012f7bad08da23e347344b4c6
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: hugocasa <hugo@casademont.ch>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
|
||
|
|
ed2ff6c5e7 |
fix: scope git-sync concurrency key per repository (#10767)
* [ee] fix: scope git-sync concurrency key per repository Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test: dedupe git repo resource helper, fail loudly on callback timeout Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: reserve the workspace prefix in the git-sync concurrency key cap Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test: cover the concurrency-key prefix reservation and the pull lane Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore: update ee-repo-ref to dff61d6da80d15f8327af99d322c00cc91f784ff This commit updates the EE repository reference after PR #734 was merged in windmill-ee-private. Previous ee-repo-ref: e50a7eca7d7f8771979485f654831b15de59ec25 New ee-repo-ref: dff61d6da80d15f8327af99d322c00cc91f784ff 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> |
||
|
|
5fb145c79f |
feat: guided setup wizard for data tables on Cloud (#10584)
* feat(frontend): guided setup wizard for data tables
On Cloud a data table cannot use the Windmill instance database, so a new
workspace hit a dead end: an alert telling the user to go find a PostgreSQL
resource somewhere else. Setting one up meant three disconnected places, and the
connection could only be tested after the config had already been saved.
Adds a three-step wizard (choose a database -> set it up -> name it) reached from
the data tables settings page:
- Supabase: signs in via the existing supabase_wizard OAuth client and creates
the project from inside Windmill. Because db_pass is an input to project
creation, Windmill sets the password and the user never visits a dashboard.
- Your own database: picks an existing postgresql resource, or adds one with a
connection string through the form that already supports it.
- Windmill database: hands back to the inline row editor, since instance
databases are provisioned by a superadmin.
Verifying access is no longer a step the user takes: Continue runs the check and
passing it is what advances the wizard, so a database that cannot create tables
never reaches the workspace config.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* chore: pin ee-repo-ref to the Supabase provisioning endpoints
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(frontend): do not claim the database is ready when its check failed
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: address review findings on the data table wizard
- The Supabase create branch advanced on `provisioning === 4` without consulting
the check it had just run, so a role that cannot create tables could reach
Finish. It now blocks and offers Try again.
- Retrying no longer mints a fresh secret variable + resource each time: the
credentials are only re-created when the password actually changed.
- The generated password is captured before the create call rather than after,
since a throw there can still leave a project behind.
- On a failed provision the project list is refreshed, so the just-created
project can be picked up from the other tab instead of provisioning a second.
- Finish refuses a name that already belongs to another data table, which
previously repointed it at the new database.
- Secrets go to the acting user's namespace instead of a literal `u/admin/`.
- The progress list no longer ticks "Created on Supabase" before the request is
sent, and does not claim the database is ready when its check failed.
- The wizard's resume state is cleared when it closes, so reopening after an
abandoned OAuth round trip is not stuck on step 2.
- The OAuth callback shares the session-storage key rather than repeating it.
- SupabaseConnect uses the shared provisioning helpers instead of a fork.
- Restores the doc comment displaced onto TestDataTableResourceQuery.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(frontend): simplify Alert layout and balance its vertical padding
The body was rendered by two near-duplicate branches, each wrapping the text in an
extra div only to hang a margin on it, and the margins disagreed: the collapsible
branch spaced above with mt-2, the static one below with mb-2. Since isCollapsed
defaults to true, every non-collapsible alert took the static branch, so titled
alerts read as 24px of space below the text against 16px above -- visibly
off-centre -- with the title and body flush against each other.
Collapse both branches into one and drop the margins; the container's own padding
now sets top and bottom equally, with a small gap under the title row.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(frontend): only offer Supabase when its OAuth client is configured
The wizard offered the Supabase card unconditionally, so on an instance whose
superadmin never configured a supabase_wizard client -- or whose backend is built
without the oauth2 feature, which compiles the whole /api/oauth router out -- the
card dead-ended at a 404. Gate it on listOauthConnects, the same check
ApiConnectForm already makes, fetched on open so configuring the client mid-session
does not require a reload.
Also drop the Supabase project ref from the existing-project cards: it is an opaque
identifier that means nothing outside Supabase's own dashboard URLs. Show the region
instead, plus a status word when the project is not healthy, since a paused project
is the one case where the connection check fails for a reason unrelated to the
password.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(frontend): run the Supabase OAuth leg in a popup
A full-page redirect unmounts the wizard, so anything the user does on Supabase's
side -- signing in, confirming an email, browsing their dashboard -- leaves them
with nothing pointing back at Windmill, and the wizard had to park its state in
sessionStorage to survive the trip.
Open the connect endpoint in a popup instead. The modal stays on screen throughout
and the callback hands the token back through postMessage rather than navigating.
The parked-state path stays as the fallback for browsers that block the popup.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(frontend): scope the connection check to the choice that produced it
A failed check stayed on screen when the user switched Supabase mode or picked a
different provider, so a fresh tab opened showing an error about a database it had
nothing to do with. Clear the report and the error on both switches; re-clicking the
tab already selected leaves an error the user is reading in place.
Also polish the Supabase step: project cards get the provider-card treatment (icon,
p-3, flex column) instead of a hand-rolled variant whose block layout left more
padding above the name than below; form labels settle on text-emphasis; and the
signup link sits under the primary button for anyone who does not have an account
yet.
Drop the "free" badge and the "Free on Supabase" line -- every option in the wizard
is free, so neither told the user anything -- and say what the Supabase card
actually does now that connecting an existing project is the default.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* refactor(frontend): one setup checklist and one Supabase step for every host
The data table wizard, the instance database modal and the resource drawer each had
their own version of the same two interactions, and they had already begun to drift:
the wizard's Supabase resource shape was rebuilt by hand in the drawer, and the
instance checks rendered with no notion of a step being in flight.
SetupChecklist replaces LoggedWizardResult, whose only consumer was the instance
modal. It adds the running state that component lacked, so a list driven by an
endpoint that reports nothing until it returns still shows where it is. Both the
instance checks and the Supabase provisioning stages render through it.
SupabaseProjectStep owns picking or creating a project, and useSupabaseOauth owns
the popup leg. Each host keeps only what is genuinely its own: the wizard saves a
variable and resource then verifies the connection, the resource drawer fills in its
own form. Both trigger authorization themselves, so a host can offer it a screen
earlier than the step does.
The lists load behind a spinner because which mode to open on depends on whether the
account has projects; deciding that after rendering flipped the toggle under the user.
Adds a kitchen_sink playground for the checklist so the animation and every failure
position can be exercised without a backend, a superadmin, or a Supabase account.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(frontend): tidy the resource drawer around the Supabase entry point
Connect Supabase was a hand-styled anchor carrying Supabase's brand hex values
rather than a Button, and it sat in a row whose other controls had settled on
unifiedSize md. Making it a Button meant SupabaseIcon had to satisfy IconType, so it
now takes `size` (deriving height/width from it) alongside the string props its other
callers pass.
The manual resource form spaced every field 32px apart and WhitelistIp added another
16px of its own, which read as a gap rather than a rhythm. One gap of 16px, with the
form itself given a little more separation from the description above it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(frontend): stop Supabase resources coming up modified when first opened
Resource forms fill in every unset property from the schema as soon as they render,
so a postgresql resource saved without region, root_certificate_pem and use_iam_auth
was dirty -- and had saved a draft -- the first time anyone looked at it. Write them
with the rest of the value.
SupabaseConnect also rebuilt the resource shape by hand instead of using the shared
helper, which is how the pooler host format ended up in two places.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat(backend): record where a data table came from and whether setup finished
edit_datatable_config replaces the whole datatables map and DataTable does not deny
unknown fields, so anything the request omits is dropped without a word. origin and
setup_incomplete would have been erased by any unrelated save;
preserve_unmanaged_datatable_fields carries them -- and migrations_enabled, which had
the same problem inline -- forward for entries that already exist, following renames.
setup_incomplete is what lets a row be recorded before the resource it points at
exists, so the wizard can write nothing until the user finishes. There is deliberately
no intermediate state: the setup runs entirely in the browser, so nothing server-side
could advance one.
datatable_health probes every data table at once for the settings page and skips the
incomplete ones, whose resource_path resolves to nothing yet. set_datatable_setup
patches a single entry instead of resending the map. test_datatable_connection_value
checks a connection the caller has not saved anywhere, which the wizard needs before
it has written a resource.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(frontend): make destructive default and subtle buttons read red
Both variants were neutral until the pointer arrived, then filled solid red: nothing
marked the button as destructive until you were already on it. They now carry red text
at rest, with a faded red border on default and a light red wash on hover, which is
what the legacy red border style in the same file had always done.
Three call sites passed color="red" alongside a design-system variant. getStyleClass
returns before colour is read for accent, accent-secondary, default and subtle, so the
delete-migration control, its modal confirm and the import-database button had all been
rendering neutral. They pass destructive now.
The dropdown variant strips the button's own border, and matched border-border-light
literally -- a class the destructive style no longer contains.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat(frontend): rebuild data table setup around a read-only row
The wizard gathers intent over two steps, reviews it on a third and writes nothing
until Finish, so a billable Supabase project is created only once the user has seen
what will happen. runSetup is also the retry: every step probes for its own result
before doing anything, so running it again on a half-finished data table resumes
instead of duplicating. Its steps are keyed rather than dispatched on their titles,
where rewording one changed what it did.
The settings row stops being an editable form with a dirty/save cycle. It carries the
name, where the database came from, a health dot and two actions; everything rare
moved into the gear panel, which also offers Finish setup for a data table whose
wizard never completed. Manage is ExploreAssetButton, the control the ducklake list
already uses, and the row and panel both link out to the underlying resource.
supabaseResourceValue no longer assembles the pooler host from the region.
aws-0-<region>.pooler.supabase.com is wrong for any project Supabase allocated
elsewhere, so the host, user and port come from the pooler config endpoint.
Two data tables sharing one database also share _wm_migrations, which is probed
unqualified, so the review step warns when the database being connected is already
behind another data table.
SupabaseConnect is deleted. The resource drawer uses the shared project step
restricted to existing projects: creating one is a billed action and belongs in the
wizard, which has somewhere to report what it did. The kitchen_sink checklist
playground goes with it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(frontend): fall back to a direct Supabase connection when the pooler cannot be read
Reading a project's Supavisor config needs the database_pooling_config_read scope, which
an instance's Supabase OAuth app may never have been granted. No retry recovers from
that, and the wizard treated it as fatal: the user was left with an error and no way to
finish connecting a project that was otherwise fine.
resolveSupabaseConnection replaces the bare pooler read everywhere it happened. Asking
for session pooling and failing now yields a direct connection plus the reason, which
supabaseResourceValue already knew how to write. Nothing about the fallback is silent --
direct is IPv6-only, which is the whole reason session pooling is the default -- so the
wizard warns on its review step and the resource drawer says so in its toast.
The row is recorded before credentials are saved, so an origin claiming session pooling
has to be corrected once a direct host is what gets written; the run patches it through
set_datatable_setup rather than leaving the panel to report a mode nothing uses.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat(frontend): open the database behind a data table, and say when it cannot write
Every database in the list now opens the surface that owns its credentials. A postgres
one opens its resource in the editor drawer; a Windmill instance one opens the instance
modal, which is where its setup checks, password rotation and drop already lived. Both
are reachable from the row and from the panel's provenance list, and the provider icon
moved inside the button so the whole thing is one target.
CustomInstanceDbWizardModal targeted #content unconditionally, which put it underneath
the panel drawer that now opens it. It takes a target, and the panel portals it to the
body.
The status column gains a third state. The probe reports privileges but nothing gated
the dot on them, so a data table whose role cannot create tables showed as Connected and
only failed when someone ran a migration. It reads "Limited permissions" instead, and
opens the panel on the report carrying the GRANTs that fix it -- the settings page has
already probed, so the panel takes that report rather than asking the user to run Test
connection over work already done. fullyPrivileged is exported from the report component
so the dot and the report cannot disagree about what counts as healthy.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* revert(frontend): keep the data tables settings table as it was
The settings table and the setup wizard are two changes that only shared a file. Splitting
them makes each reviewable: this branch keeps the wizard, and the read-only row, gear
panel, health probe and clickable databases move to their own branch.
The rows go back to the editable form with its pickers and save footer, still opening the
wizard from Add a database. DataTableSettingsPanel, dataTableHealth and dataTableOrigin
had no other consumers and go with them; the connection report stays, because the wizard
shows it too.
DataTableSettingsType keeps `origin`: the wizard writes it, and the review step reads it
back to warn when two data tables would share one database and therefore one
_wm_migrations table.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(frontend): confirm before dismissing the data table wizard mid-setup
Closing was guarded while a run was in flight and unguarded before one, which is backwards:
a run leaves a row to resume from, whereas a backdrop click on the review step threw away
the project, the pasted password and the folder with nothing to recover them from.
Backdrop, Escape and the close button now go through one path that asks first. It only asks
when there is something to lose -- no provider chosen yet, or a run that already produced a
result, closes immediately -- so the dialog does not become something to click through.
Continue in the background still leaves in one click; that exit was always the deliberate
one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(frontend): stop the wizard claiming the resource folder controls who can use a data table
"Who can use this database" was wrong. Every path that resolves a datatable:// reference --
both executors and the agent-worker endpoint -- reads the resource unchecked, by workspace
and name. A resource in u/admin is usable by everyone's scripts. The folder governs who can
see and edit the connection, and who can reference the resource directly in a SQL step;
neither is who can use the data table. The wizard was contradicting the tab's own
description two screens later.
The folder select and name field become one Path picker, the same one the resource,
variable and script forms use, so the review step reads as a resource path rather than a
permission choice. Its initialPath is snapshotted when the step opens: Path seeds itself
from it, and a live value fights the typing. Finish now also gates on Path's error, so a
taken or malformed path stops the run before it writes anything.
The button that opens all this says "Add a data table" -- the data table is what you get;
the database is a detail chosen along the way.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* revert(frontend): move the destructive button restyle out of the wizard PR
This reverts
|
||
|
|
a7637aca31 |
chore(main): release 1.792.2 (#10753)
* chore(main): release 1.792.2 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> |
||
|
|
fa7fbd348d |
fix(security): validate ansible git repository URLs before invoking git (#10759)
The Ansible executor passed the user-controlled git repository `url` (from playbook YAML or a `git_repository` resource) straight into `git clone`, `git ls-remote` and `git remote add` on the worker host. A URL that git parses as an option — e.g. `--upload-pack=<cmd>` — turns `git ls-remote <url> HEAD` into arbitrary command execution on the host, outside any job sandbox. Non-http transports (`ext::`, `file://`, local paths) similarly run programs or read host files. Add `validate_git_repo_url` in windmill-common: reject a leading `-`, reject remote-helper `::` syntax, and allow only the `http(s)`, `ssh`, `git` and scp-like `[user@]host:path` transports. Also reject a `branch`/`commit` that starts with `-`. Validation runs at every ansible entry point that spawns git, covering both the inline-YAML and resource-provided URL paths. CWE-88 (argument injection) / CWE-78. Reported by Nitin Gavhane. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
f34b7fbcfa |
fix: make the listScripts parent_hash filter valid SQL (#10752)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
9f517d5a40 |
chore(main): release 1.792.1 (#10750)
* chore(main): release 1.792.1 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> |
||
|
|
8efede55d6 |
chore(main): release 1.792.0 (#10745)
* chore(main): release 1.792.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> |
||
|
|
ef4dc46d4b |
fix(cli): keep script settings on push and repair the up-to-date check (#10741)
* fix(cli): keep script retention, debounce and cache settings on push Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(cli): surface the create response when the fixture fails Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(cli): drop debounce settings the CI build refuses to accept Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * repair the script push up-to-date comparison (#10743) * test: settle the backlog before the capped audit-export drain (#10737) * test: settle the backlog before the capped audit-export drain * chore: update ee-repo-ref to bd4de74eb37b32a2b6c7c69f6dedac031ef8436b This commit updates the EE repository reference after PR #730 was merged in windmill-ee-private. Previous ee-repo-ref: b5a5f9114df26088cfe976d91f10e55ba8bfcaa6 New ee-repo-ref: bd4de74eb37b32a2b6c7c69f6dedac031ef8436b Automated by sync-ee-ref workflow. --------- Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com> * fix(cli): repair the script push up-to-date comparison Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(cli): drain dependency jobs and pin a non-1 priority skip Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(cli): describe the priority fixture without the old comparison Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor(cli): read cache_ignore_s3_path off the typed response Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(cli): stop redeploying bunnative scripts on every push Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com> |
||
|
|
6783a396b1 |
fix(api): document cache_ignore_s3_path on the Script read schema (#10742)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
1fa3bf3b29 |
fix: show runtime-detected assets in a run's Assets tab (#10738)
* fix: show runtime-detected assets in a run's Assets tab * fix: address review nits on run assets tab * fix: cap the run assets list and report when it is cut * fix: cap run assets by asset, not by row |
||
|
|
5d7881beb8 |
test: settle the backlog before the capped audit-export drain (#10737)
* test: settle the backlog before the capped audit-export drain * chore: update ee-repo-ref to bd4de74eb37b32a2b6c7c69f6dedac031ef8436b This commit updates the EE repository reference after PR #730 was merged in windmill-ee-private. Previous ee-repo-ref: b5a5f9114df26088cfe976d91f10e55ba8bfcaa6 New ee-repo-ref: bd4de74eb37b32a2b6c7c69f6dedac031ef8436b Automated by sync-ee-ref workflow. --------- Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com> |
||
|
|
ce71756c89 |
chore(main): release 1.791.0 (#10718)
* chore(main): release 1.791.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> |
||
|
|
343ce6e143 |
fix: derive a raw app's policy on deploy, and default an omitted execution_mode (#10733)
* fix: default an omitted app policy execution_mode to publisher Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: drop stale comments claiming execution_mode is required Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: derive a raw app's policy on deploy instead of trusting the caller's Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore: pin the ee ref to the companion branch Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: vendor the raw-app policy derivation into the bundle job Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: note the vendored raw-app policy bundle Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: derive the policy on a value-only raw-source update too Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: reject raw-app runnables whose shape yields an unusable grant Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: cache the new policy query and tighten raw-app runnable validation Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: let the policy bundle drift guard survive a CRLF checkout Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore: update ee-repo-ref to 23431f5cf1d627051ded89111bbf2e301e9db456 This commit updates the EE repository reference after PR #729 was merged in windmill-ee-private. Previous ee-repo-ref: 0bdf8818fa115ad6b0d14f3117a18e8a580cce4d New ee-repo-ref: 23431f5cf1d627051ded89111bbf2e301e9db456 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> |
||
|
|
6b5b9f72d4 |
fix: type s3-streamed columns that are all-null in the inference sample (#10728)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
ab3c0206d7 |
fix: support @typechecked decorator in Python relative imports (#8495)
WindmillFinder's ModuleSpec lacked origin, so __file__ was never set on loaded modules. inspect.getfile() then raised "is a built-in module", breaking typeguard's @typechecked and anything else that introspects module source. Use spec_from_file_location() which sets origin correctly. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: hugocasa <hugo@casademont.ch> |
||
|
|
010d67e07f |
chore(main): release 1.790.1 (#10712)
* chore(main): release 1.790.1 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> |
||
|
|
529e960629 |
perf: cap resource content sent to the search modal (#10714)
* perf: cap resource content sent to the search modal Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: address review — fence the LATERAL, flag partial search, add cap test Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: pluralize the truncation notice and link the cap to its openapi doc Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
0258f3f81b |
perf: unblock workers before the API router is built (#10711)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
944ad1083a |
chore(main): release 1.790.0 (#10699)
* chore(main): release 1.790.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> |
||
|
|
3468cb68b1 |
fix: drop sampling params on Claude models that reject them (#10708)
* fix: drop sampling params on Claude models that reject them Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: scope the sampling-param claim to what was probed and split the bedrock test Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test: build the disable body through the resolver instead of asserting a rejected shape Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: use the Gemini 3.1 Pro id that actually resolves Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: Bedrock Sonnet 5 cannot disable thinking, unlike the native API Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
c9ddddda1b |
log the settings a failed read left unapplied (#10709)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
d97f380c87 | test: stop stranding sqlx pool permits in run_in_isolated_thread (#10707) | ||
|
|
ee533273dd |
fix: confine jobs:run tokens to the jobs of the runnables they may start (#10635)
* fix: confine path-scoped jobs:run tokens to their runnable's jobs Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: project singlestepflow onto its runnable and confine kind-only run scopes Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep every by-id job read reachable by a jobs:run token Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: whitelist the dbt and wac-approval by-id job reads for run tokens Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: let an apps:run scope satisfy job-read confinement for that app's runs Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: apply run-scope confinement on top of the approval-token read bypass Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: confine the resume-secret job reads to the run scope as well Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
3f07a1a803 |
feat: let the global AI chat call connected MCP servers as the user (#10656)
* feat: let the global AI chat call connected MCP servers as the user Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: address review findings on the chat MCP tools Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: connect MCP servers from a predefined list in chat and agent steps Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: show the OAuth redirect URL in the instance connect settings Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: clarify the OAuth redirect URL copy in instance settings Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: match the instance settings warning style and drop the redirect tooltip Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: use the standard warning alert for the redirect url mismatch Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: correct the GitHub token guidance in the MCP registry Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: warn when an OAuth connect lacks the scopes an MCP server needs Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: request the connect's scopes when the oauth popup is opened directly Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: connect an oauth-app MCP server without leaving the panel Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: seed connect scopes from the instance config only Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: make the chat use only the MCP servers you turn on Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor: align the MCP connect UI with the design system * feat: make a pasted url the default way to connect an mcp server * feat: show provider icons on the suggested mcp servers * fix: make both mcp sign-in paths behave the same and stop reloading on toggle * fix: clarify the mcp tool step's server field and drop its info alert * fix: name the mcp resource in the tool step and move the transport note into the connect box * fix: drop the redundant description on the mcp resource field * fix: make the mcp connections trigger icon-only * fix: scope enabled mcp servers to the account and address review nits * fix: wait for connect scopes and create session connections in the operating workspace * feat: move mcp connections into the chat's plus menu and fix review findings * fix: show mcp servers as checkboxes so off reads as a state * feat: give menu rows an on/off switch and use it for mcp servers * fix: lead the mcp menu rows with the switch * feat: keep the menu open while toggling and simplify the connect card * fix: ask for the server before the credential in the connect card * fix: show one credential path at a time in the connect card * fix: label the path field and move token guidance into its tooltip * fix: open straight into connect and keep the server menu scannable * feat: warn when an mcp connection lands outside your own space * refactor: require the workspace on the mcp connect components and rename the oauth child * fix: replace the oauth variable on reconnect and bound every mcp result * feat: show a connected server's provider icon in the connections list * feat: resolve mcp provider icons from the url and clarify the path field * style: align the mcp connect card with the design system surfaces * style: drop the redundant oauth support line and name the scopes oauth scopes * feat: keep the mcp connect card open in the connections drawer * feat: preopen the mcp connect card under the agent step resource picker * feat: resolve a typed mcp url to its registry entry and describe the token field * style: name both mcp connect actions connect * style: name the mcp oauth actions connect with the provider * style: say in the path description what the connect action will save * style: name the resource type in the mcp connect path description * feat: cache mcp provider icons and confirm disconnect in a modal * fix: keep the mcp menu switches live and the disconnect modal above the drawer * style: fall back to the plug icon in the mcp menu rows * fix: never destroy a foreign variable or resource when connecting an mcp server * fix: prove a token variable is ours before writing it and bound mcp search failures * fix: pin an mcp oauth popup to the target it was opened for * fix: bind an mcp credential to the server and popup it was requested for * fix: bound mcp tool calls with a deadline and drop stale server listings * fix: keep the disconnect confirmation handler returning void * fix: tie the mcp tool cache to the resource revision and the grant to its scopes * fix: verify mcp read-only server-side, keep oauth connector mounted Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
53eb94659b |
feat(telemetry): extend feature-usage tracking beyond AI features (#10681)
* feat(telemetry): extend feature-usage tracking to long-tail features Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: describe telemetry as product feature usage rather than AI usage Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor(telemetry): trim disclosure copy and drop unused pick origin Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(telemetry): count trigger fires per run and key hub picks from hub data Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(telemetry): slugify hub keys and order both writers' upserts Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(telemetry): key native trigger adoption by service so it matches fires Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore: update ee-repo-ref for native trigger adoption fix Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor(telemetry): move feature-usage collection into the ee crate * docs: point feature-telemetry at the moved registry and rust writer * docs: correct the trigger-fire gate comment to match measured step counts * docs: put the private-build caveat on the verification step * chore: update ee-repo-ref to f079db9e7962a413b349c4ff8036080894f30771 This commit updates the EE repository reference after PR #725 was merged in windmill-ee-private. Previous ee-repo-ref: 055adb80416f9339c9a28ae7fbaeadad30d74959 New ee-repo-ref: f079db9e7962a413b349c4ff8036080894f30771 Automated by sync-ee-ref workflow. --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Ruben Fiszel <ruben@windmill.dev> Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com> |
||
|
|
98bacab907 |
refactor: combine the per-minute counters onto one shared helper (#10687)
* refactor: combine the per-minute counters onto one shared helper Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep dashmap in windmill-store for the azure devops token cache Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor: name the sweep counter for what it counts Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
bd5b3ea779 |
fix: send sage_intacct oauth client credentials in the request body (#10685)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
b5510333ea |
fix(groups): replace instance-group delta-patching with a state-based reconciler (#10686)
* fix(groups): replace instance-group delta-patching with a state-based reconciler Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TYi5MGYwUL6gjYMgQVY2Yx * fix(groups): follow instance-group renames through workspace references Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TYi5MGYwUL6gjYMgQVY2Yx * fix(groups): preserve historically-orphaned instance-group members on upgrade Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TYi5MGYwUL6gjYMgQVY2Yx * fix(groups): preserve retained-group orphans too in the upgrade migration Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TYi5MGYwUL6gjYMgQVY2Yx * test(groups): exercise the orphan-preservation migration; strip refs before converting Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TYi5MGYwUL6gjYMgQVY2Yx * test(groups): pin the migration's strip-before-convert order Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TYi5MGYwUL6gjYMgQVY2Yx * fix(groups): make reconciliation the last locking step in every mutation path Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TYi5MGYwUL6gjYMgQVY2Yx * fix(groups): make the workspace advisory lock first in the lock hierarchy Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TYi5MGYwUL6gjYMgQVY2Yx * fix(groups): lock workspaces before membership writes in single-user paths Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TYi5MGYwUL6gjYMgQVY2Yx * fix(groups): use the instance_group row as the group-level mutex Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TYi5MGYwUL6gjYMgQVY2Yx * fix(groups): take an exclusive instance_group table lock in overwrite_igroups Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TYi5MGYwUL6gjYMgQVY2Yx * chore: update ee-repo-ref to af02d6bce55512b65c56adcbf69a8e15cd124d23 This commit updates the EE repository reference after PR #726 was merged in windmill-ee-private. Previous ee-repo-ref: ec2feac82636869731666e5c6578b6c078e9aeb2 New ee-repo-ref: af02d6bce55512b65c56adcbf69a8e15cd124d23 Automated by sync-ee-ref workflow. --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com> |
||
|
|
68fc7825bb |
fix: refresh AI provider model defaults and capability metadata (#10690)
* fix: refresh AI provider model defaults and capability metadata Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: send explicit thinking disable for Claude and cap Opus 4.1 output Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: resolve mistral-medium-latest window and OpenRouter Claude 5 off Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: cover au. bedrock geo and Fable 5 caching, revert unverified mistral ladder Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: scope the Anthropic explicit disable to models that think by default Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: translate the reasoning off sentinel on the backend Anthropic path Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: translate the reasoning off sentinel on the Bedrock Converse path Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor: share the reasoning off sentinel and make its translation testable Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
30f5d2e766 |
perf: declare a settings pass instead of reading one setting at a time (#10698)
* perf: read global_settings once per settings-load pass `initial_load` reads several dozen settings back to back, one `SELECT value FROM global_settings WHERE name = $1` each: 50 serialized round trips before a worker is ready, 32 before a server is. On localhost that is ~20ms and invisible; against a real database it is 50x the RTT per process start, which `EXIT_AFTER_N_JOBS` turns into a per-job cost. `with_global_settings_snapshot` reads the whole table (12 rows on a typical instance) into a tokio task-local, and `load_value_from_global_settings` serves from it. Scoping it to the task is what keeps the single-setting reload paths correct: a `notify_global_setting_change` event for one key runs outside any scope and still reads the database, so a live settings change reaches a running worker as before. Agent workers hold an HTTP connection with no snapshot to take and are unchanged. `load_smtp_config` and `reload_custom_tags_setting` had their own inline copies of the same query; they go through the shared loader so they land in the snapshot too. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: state the snapshot contract on the reader and the query `load_value_from_global_settings` is called from ~10 crates and one of them writes a setting then immediately re-reads it through `reload_custom_tags_setting`; say on the function itself that a scope, when one is installed, serves the read and leaves `db` unused. The query comment claimed the table is a handful of rows. It is not bounded that way: `workspace_dependencies_map_rebuilt:<workspace_id>` adds a row per workspace and never removes it. Those dynamically named rows are also why the snapshot fetches the whole table instead of the wanted names, so state that as the reason. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: bound the settings snapshot and keep it out of two reads Three review findings, all real: The snapshot fetched the whole table, which is not bounded by the settings that exist: `workspace_dependencies_map_rebuilt:<workspace_id>` adds a row per workspace with no cleanup path, and no settings pass reads one. It now fetches only statically named rows, and reads of a `<prefix>:<id>` name skip the snapshot and go to the database. Correctness does not rest on that naming convention — a colon-free dynamic name would simply be in the snapshot and still answered correctly — only the bound does. A snapshot query that failed inside an enclosing snapshot awaited the body bare, so its reads were served by the outer snapshot rather than falling through as documented. The task-local carries an explicit bypass state and the failure path scopes it. `reload_jwt_secret_setting` decided whether to generate-and-upsert the JWT secret from a snapshot-served read, so a replica booting alongside another could overwrite the secret it had just generated and invalidate its tokens. That read goes through the new `load_value_from_global_settings_fresh`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep the snapshot query on the primary-key index `name NOT LIKE '%:%'` bounded the rows returned but not the work: a leading wildcard cannot use the index, so Postgres read every row anyway. Against 50k dynamically named rows it plans as a seq scan of 516 buffers whether or not seqscans are enabled — and worker connections disable them, so the plan was one the query shape forbade rather than one the planner chose. `name = ANY($1)` over an explicit list plans as a bitmap index scan, 7 buffers, bounded by the listed names rather than by table size. That list is also exactly the set the snapshot may answer from, so a name outside it falls through to the database instead of reading as unset: listing a setting is a performance choice, never a correctness one, which is what keeps the list safe to maintain by hand. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor: declare a settings pass instead of reading one setting at a time Replaces the prefetch-list snapshot with a pass the call sites build themselves. `SettingsPass` collects the reads `initial_load` will make as `(name, applier)` pairs, fetches them together, then replays the appliers in declaration order. Declaring is what makes the batch exact. The same `if server_mode` / `if *CLOUD_HOSTED` / `cfg` branches that used to guard a read now guard a declaration, so the fetch asks for what this process needs and nothing else, and there is no list of setting names to keep in sync with anything. Ordering is preserved end to end: appliers run in the order they were declared, and non-setting work in the middle of the sequence keeps its place as a step, so nothing moves and nothing runs twice. Steps that need several settings at once take them together. The batch distinguishes three states where a per-setting read only ever produced two at a given call site: - a value, - genuinely unset, which several settings must see in order to restore a default when the setting is cleared, - could not be read, which must leave the in-memory value alone. Collapsing this into "unset" would let one failed query reset workspace fairness and the queue caps across a cluster. Over HTTP the reads go out together rather than sequentially, so an agent worker's settings load costs one round instead of ~36, with no new endpoint. A setting an agent may not request still resolves to unset, as the per-setting call returned for it. `reload_*` keeps working per setting for the notify path, sharing its apply half with the pass. The wrappers no caller was left using are dropped. worker startup: 50 queries -> 2 (the batch, and jwt_secret which stays its own read so the pass cannot sit between reading it absent and upserting a replacement over another replica's). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: run the pass's non-setting steps in declaration order too Review round found the settings pass had a gap: the reads were declared but the work interleaved between them still awaited inline, so it all ran before `pass.run` applied anything. `manage_audit_partitions` therefore saw `AUDIT_LOG_RETENTION_DAYS` at its compile-time default rather than the configured value, and dropped every partition past that default. An instance keeping 30 days on CE lost the 14-to-30-day band on startup and on every full-reload tick. The `STORE_AUDIT_LOGS_S3` export anchor had the same cause: the gate read `false` before the setting applied, so an env-var-enabled export never anchored and its first tick skipped the rows committed before it. `action` exists so a step keeps its place in the sequence; every remaining inline await is now one, which fixes both and leaves no phase where a read can observe a value the pass has not applied yet. Two more from the same round: A batch that fails as a whole now falls back to per-setting reads. Skipping every applier preserves known-good state on a reload tick, but a starting process has none, and would have run on compile-time defaults until the next full reload twelve hours later. `FORCE_RUBY_REPOS` is honored again: the batched url-list path parsed without the `FORCE_` check its per-setting counterpart applied, so the override was silently dropped. `load_setting_value` never had one, so the third helper was never affected. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: declare the object-store and worker-config steps in the pass too Two awaits were left running ahead of `pass.run`, so the settings they read were still at their compile-time defaults. The object-store reload is the one that matters: an AWS OIDC store mints its first token against an issuer built from `BASE_URL` (`oidc_ee.rs`), and with `OTEL_ENVIRONMENT` set nothing loads that before this pass does, so the store signed with the unset default, left `OBJECT_STORE_SETTINGS` empty and fell back to the ten-second retry while startup carried on. `reload_worker_config` calls `store_pull_query`, which reads the workspace fairness knobs. It happened to converge because the enabled flag re-stores the query when it changes, but it was reading defaults on the way there. Both are steps now, which is also what the earlier fix should have covered: the only await left outside a step is `pass.run` itself. Also from the same round: `fetch_settings_batch`'s doc comment had been stranded on the helper inserted above it, and the batch-failure fallback re-ran the same reads on an agent worker, where the batch already is the per-setting read. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: point the setting-loader docs at functions that still exist `reload_setting` went with the other wrappers no caller was left using, but two doc links still referenced it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: decide the jwt secret in sql so the read can be batched `reload_jwt_secret_setting` generated a secret whenever its read came back absent or unparseable, and upserted it unconditionally. Two replicas booting against an empty row therefore each installed their own and rejected each other's tokens, and the same happened on a running cluster whenever the row was deleted or set to a non-string. Keeping the read next to the write kept the window narrow but never closed it, and it was the reason this one setting could not go through the settings pass. `get_or_create_jwt_secret` puts the decision in the statement instead: INSERT ... ON CONFLICT (name) DO UPDATE SET value = EXCLUDED.value WHERE jsonb_typeof(global_settings.value) <> 'string' RETURNING value First writer wins, a usable secret is never overwritten, and an empty RETURNING is how a caller learns another process's secret stands. The `WHERE` also keeps a normal startup from writing at all, which matters because `notify_global_setting_change` fires on every write to this table and an unconditional upsert would have made each start trigger a cluster-wide reload. Because the statement decides rather than the caller's read, a stale value is harmless and `jwt_secret` is now an ordinary declaration. Worker startup is a single batch round. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep a failed read from dropping a FORCE_ override or clearing a setting Two ways a read that did not succeed was being treated as an answer. A `FORCE_` override used to be checked before the read, so a failed read could not affect it. Moving that check into the parser put it behind a value arriving, and a failed read skips its applier, so a forced private registry fell back to the public index and a forced `settings.xml` was deleted from disk by the Maven step that follows it. Forced settings are declared as steps with no read now: the override outranks the database, so there is nothing to fetch and nothing to lose when a fetch fails. The setting loaders were passing `v.ok().flatten()` to their appliers, which turns a database error into "unset". Most appliers ignore `None`, but `apply_tag_per_workspace_workspaces` clears the workspace whitelist with it, making every workspace eligible for per-workspace tags, and `apply_fork_workspace_tag_append_fork_suffix` stores `false`. Both are also reached from the notify handlers, so a blip during a reload changed routing for the cluster. They take `?` now, as the code they replaced did by leaving the error arm empty, and the other five are converted with them so an applier that later grows a `None` branch cannot inherit the problem. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: route hub_api_secret through the FORCE-aware declaration `HUB_API_SECRET` lives in an `ArcSwap` rather than an `Arc<RwLock<_>>`, so it could not use `option_setting` and was declared by hand with a bare `setting` plus `parse_option_setting_value` — which is exactly the path that skips the `FORCE_` handling, so a failed read still dropped `FORCE_HUB_API_SECRET`. The rule now lives in `option_setting_with`, which takes the store closure and leaves `option_setting` a wrapper over it, so a setting held in something other than an `RwLock` reaches it too rather than having to reimplement it. The three remaining hand-written parses are `parse_setting_value`, which has no `FORCE_` handling to miss: `load_setting_value` never had the check either. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
633d7bcb2e |
feat: add trigger_history table with source tracking (#10696)
* feat: add trigger_history table with source tracking Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: gate trigger history reads on scopes and harden its writers Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: filter trigger history scopes in SQL and match the cleared-handler diff Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: record a trigger restore from the trashbin in its history Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: record bulk http trigger creates and document the recording boundary Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: lock the trigger row when capturing its history preimage Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: only record an auto-disable that actually flipped the schedule Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore: state the auto-disable invariant once instead of at four call sites Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: render trigger history changes as a structured field diff Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: make a server-initiated disable atomic with its history row Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: note that the auto-disable savepoint takes no pool connection Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: note the flow fallback is the last chance to disable Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: never leave a trigger enabled because its history row failed Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: retry the disable history row instead of dropping it on first failure Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: use the design-system Button for the change-value expander Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: hold the trigger row lock across its disable history row Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep the history-loss alert out of the listener cancellation race Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: read the history workspace through the trigger-workspace seam Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
6d03784d4b |
fix: keep non traffic-serving processes out of coordinated restarts (#10694)
* fix: key server_heartbeat row on hostname so restarts reuse one row Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore: trim announce_server_started doc to the durable constraints Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: only traffic-serving processes take part in coordinated restarts Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore: name every non traffic-serving mode in the restart-gate comments Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore: narrow the restart-gate comments to claims that hold Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
878b8ef4c4 |
perf: cache resolved python interpreter path across worker restarts (#10701)
* perf: cache resolved python interpreter path across worker restarts Every worker process start spawned two `uv python find` subprocesses to re-discover an interpreter path that had not changed, and every python job spawned one more. The resolved paths are now memoized in a small JSON file next to PY_INSTALL_DIR, which outlives the process, so a restarted worker (notably under EXIT_AFTER_N_JOBS) reuses what the previous one resolved. An entry is only served when the uv binary is the same one that produced it and the interpreter is still on disk; otherwise it falls through to a real `uv python find`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: address review findings on the python path cache - resolve uv through PATH on windows, where `metadata("uv")` looked in the worker's current directory and silently disabled the cache - stat uv with tokio::fs instead of blocking the runtime, and compute the identity once per resolution instead of once per read and twice per write - store one file per version instead of a shared map, so workers resolving different versions concurrently cannot drop each other's entry Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep the windows uv PATH probe off the async runtime The lazy static resolving uv through PATH stats candidate entries synchronously, so its first use is moved onto a blocking thread. Also records why an entry keyed on a minor-only version does not pin a patch: uv answers such a request with its minor-version link and re-points it on a patch install, so the memoized path follows the upgrade. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |