mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-21 08:02:38 +00:00
f6c4ebbfb2b74070a59a7b894aeaf708fce9f5cd
3618
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
f00b2fcb1e |
feat: drafts follow their item through a move; behind means base ≠ head (#10577)
* refactor: give home multi-select a reserved gutter and a menu entry Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep checkbox theming and reserve the gutter on non-selectable rows Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: carry every draft with an item when it moves Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: move draft-only items and warn editors when an item moves Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor: put the home selection checkbox back in the kind icon slot Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014FaEacdxR6M6VDej6C9r39 * feat: animate the home bulk bar and exit selection at zero Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014FaEacdxR6M6VDej6C9r39 * fix: keep dialog icon badges round and the panel inside narrow viewports Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014FaEacdxR6M6VDej6C9r39 * fix: address review findings on the draft-carry path Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014FaEacdxR6M6VDej6C9r39 * fix: keep a staged rename when a move carries the draft Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: restamp only the deployer's own carried draft Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: scope the moved-save restamp to the mover as well Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: read the app move's author from the head version, not the draft's base Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: carry a flow draft's baseline path so deploying it cannot un-move the flow Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: reject unsupported kinds in move_draft, survive NUL-poisoned draft rows Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: skip NUL-poisoned rows in every draft-value rewrite, not just the first Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: report a NUL-poisoned draft on move instead of 500ing Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: name the attempted operation in the NUL rejection message Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor: drop dead selection code and comments that outlived their state Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: describe script staleness as head-pinned, which is what the loader does Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: correct the third staleness comment left claiming a stable fork base Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: address CI review — auth order, save race, carry failure, path validation Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: gate operators earlier, skip the write tx without lineage, unblock a chained move Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: run the post-write moved re-assert under RLS, not the raw pool Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test: pin the moved answer to what the saver can see The post-write re-assert names a path and a username, and nothing at any layer stopped it reading them off a raw pool connection. Swapping the transaction back to `db.begin()` compiles and passes everything else, so the guard has to be a test: a non-admin saving at a path whose item moved into a folder they cannot see gets `saved`, while the admin gets `moved`. Also drops two doc comments still arguing that clearing the write gate at the old path removes the need for an RLS envelope. It does not — the gate resolves the old path and the re-assert asks about the new one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: name the real deploy path and stop restating the RLS constraint `update_path` is not a symbol in this repo; a script move goes through `create_script`. The re-assert's comment re-derived the disclosure argument that already sits on `resolve_moved_to_in`, where a caller would break it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: state the RLS and restamp constraints once each The RLS envelope was argued at three sites in drafts.rs; it now sits only on `resolve_moved_to_in`, whose signature is what a caller would break. The restamp scoping was copy-pasted at all three deploy call sites while already documented in full on `move_drafts_for_path`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: carry both path keys on a move, and grant the draft sequence The upsert now runs as `windmill_user`, so it calls nextval on `draft_id_seq` as that role. The only thing granting that is the ALTER DEFAULT PRIVILEGES in 20250205131523, whose DO block swallows failures — so an instance where it errored would fail every autosave with `permission denied for sequence`. A draft value carries two path keys: the typed one and a mirror the editors keep in step with it while it differs from the row's path. Rewriting only the typed one left the mirror naming the old location, and the loaders prefer the mirror — reopening a moved session script restored the old path and the next save un-did the move. Both keys now follow, in the move endpoint and in the passive carry, under the same tri-state rule. `typed_path_field` answered `draft_path` for every non-script kind, including resources, variables and triggers, which have no such key. It returns `None` for them now, and `move_draft` reads its guard off that mapping so the movable set and the field mapping cannot drift apart. Also documents that `move_drafts_for_path` mutates every owner's row and enforces nothing itself, and parses the draft payload once per save instead of three times. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test: pin the two-key move, and stop the down migration breaking instances Revoking the sequence grant would strip a privilege a healthy instance had before this migration ran — the grant it adds is indistinguishable in the catalog from the one ALTER DEFAULT PRIVILEGES gives at creation time — so the down is a comment, matching the other grant-only migrations. The mirror rewrite is spread over three sites that have to agree and fails silently when they don't, so it gets a test: a draft carrying both path keys has both moved, and one carrying neither mirror does not gain one. It reads the value back over HTTP rather than with `sqlx::query!`, which would need an offline cache entry of its own. Also drops twelve `.sqlx` entries this branch added and then superseded, and corrects the doc and openapi text that still described only the typed path being rewritten. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: point the empty down at the grant it is declining to revoke Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: drop the restamp and tri-state; a move relocates the draft row only A deploy that renames an item is a deploy like any other: every draft on the item goes stale, and the stale prompt with its diff is the single mechanism to catch up. move_drafts_for_path now touches only the row's path column, so the value keeps the base version the draft actually forked from, and the "moved" patch carries no version restamp. DraftBaseVersion shrinks to the three per-kind lineage fields. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * feat: stale prompt links to a diff that names and lets you pick the deployed version The stale-draft prompt gains "See what changed", which opens the diff drawer. The drawer resolves the deployed side by the draft row's own path (not the typed path, which after a rename still names the archived row), labels which version the left pane is, and offers a picker over the item's deployed history for scripts, flows and raw apps. The history endpoints return created_by (and created_at for apps) so each entry can name its deployer. "Restore to deployed" moves to the header actions. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * test: move_to asserts the response status Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: keep a script draft's base at the version it forked from The script editor seeded the draft's parent_hash from the deployed head on every load, and the next autosave persisted it, so a draft behind the deploy read as up to date after being opened once. The base now comes from the draft when one exists; the head is only used for a fresh checkout or an explicit topHash. Deploy already fetches the live head and confirms on mismatch, so the base is what makes that check meaningful. The webhook "run this version" URL uses the deployed hash rather than the draft's base. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * feat: store the version a draft forked from in one draft.base column Every kind kept its fork base under a different name and type inside the value: parent_hash (hex) for scripts, version_id for flows, parent_version for apps. draft.base holds it as one text id, derived on save from the value so every writer fills it the same way, backfilled by the migration (rows holding a NUL are skipped, since ->> raises on them). The get-by-path overlay exposes it as draft_base and the drafts list as base; the editors and the compare page read that one field and compare it to the head as text. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * feat: raw-app drafts carry a fork base, so behind means base != head for them too The raw-app bundle never carried the version it forked from, which left raw apps on the timestamp check that self-heals as you type, and the header's deploy guard read a version prop nothing set, so deploying over a newer version never asked. The route now stamps parent_version into the bundle (the draft's own base when it has one, else the head), the server derives draft.base from it, the stale prompt compares it to the head and links to the diff, and the editor threads it to the header so the deploy guard confirms. A deploy re-pins the base to the version it wrote. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * feat: refuse a rename onto a path that already holds a draft A draft occupies its path the way a deployed item does: a never-deployed item, or a draft left on an archived script. Renaming onto it would either merge two items or leave the losing row stranded at a path its item has left. The move now refuses with a BadRequest inside the deploy's transaction, so the rename itself fails and the source stays deployed. Every draft on the item then moves; there is no longer a left-behind count to report. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * feat: save drafts by row id, so an open editor follows its draft through a move A rename carries every draft on the item to the new path. An editor left open across it was still saving by the path it opened on, which the server had to refuse and answer with where the item went (the "moved" handshake and its modal). The draft row has an id: the get-by-path overlay now returns it as draft_id, every later save sends it, and the server writes the row wherever it is and answers with that path. The editor then follows: it flushes what it holds, tells the user, and navigates to the item's new path, where the stale prompt says what changed. The lineage-based move resolvers, the moved status and the moved modal are gone. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * feat: the out-of-date prompt names both versions and can take the latest as the new base The prompt now says which version the draft forked from and which is deployed (and by whom), instead of two timestamps, and gains "Take latest, keep my edits": the draft's base moves to the head and its content stays, so the user can acknowledge a newer version without discarding their work. Each route sets its kind's base field on the draft value and persists it; the raw-app bundle carries it already, so setting the state is enough there. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * feat: two-action out-of-date prompt; taking the latest moves into the diff drawer Four buttons made the prompt hard to read. It keeps "See what changed" and a red "Use latest" (it replaces the draft); closing it is keeping the draft. "Take latest, keep my edits" moves to the diff drawer's header, offered only while the draft is behind, so the user takes the latest with the diff in front of them. Scripts, flows and raw apps pass the action through their diff drawer; the classic app editor has no drawer wired to the prompt and loses it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * chore: drop the draft_id_seq grant; the draft upsert runs on the raw pool Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: a moved draft's path keys follow it, and a refused rename names the draft's owner Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: follow a moved draft on tab close, and deploy a followed flow at its new path Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: write a followed draft by id against the row's own path keys; keep base on assign and clone Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: look up a script's head at its row path, and show flow and app version ids bare Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: session editors save by draft id; raw apps keep a legacy draft's base unknown Also advance the raw-app base on deploy, relocate once per move, drop the hoisted operator check and the unread base on drafts/list. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: guard a base-unknown raw-app deploy against the head at load; keep the base in session hydration Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor: the server follows a moved draft through a move record, not client-sent row ids A move writes old path -> new path (per workspace and kind, per owner for a draft-only move) in its transaction; a draft save or discard addressed to a path the caller has no draft at resolves through it and keeps the moved draft's path keys. Creating an item at a path drops the records leaving it. Every writer (edit routes, sessions, chat, CLI, the tab-close flush) follows without passing an id, so the id plumbing is gone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: session loaders keep a draft's base, and a failed relocation flush stays put Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: a draft-only app move refuses the other app kind; a session keeps an unknown base unknown Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test: pin a teammate's carried draft; name the kind that refuses a draft move Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: an unknown base stays unknown in every loader, and an owner move extends an item move Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: a clone keeps only a base it can resolve; a base-unknown script deploys without a false guard Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: a workspace clone sanitizes a NUL-bearing draft instead of copying it unstripped Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: move records follow an account rename and deletion; a legacy draft says why it cannot move Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: an owner move extends only the item's own route, not another user's Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: a redeploy ends a route off its path, take-latest persists on raw apps, stale picker loads are dropped Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: a poisoned draft's path keys follow a move, legacy only bypasses routing on a delete, picker loads are generation-guarded Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: count picker load generations, and report a skipped legacy upsert as a conflict Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: a legacy discard follows the item's move record too Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: a failed version load keeps the picker on what the diff shows; one spelling for a legacy delete Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: the picker marks the version on display as head, restore compares the head, relocation follows the last move Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: say so when a version fails to load in the diff picker Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: take latest re-reads the head at click time; type the kept head as prepared diff data Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: taking the latest moves the head each editor knows, not just the base Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: take latest adopts the head the diff shows, and is offered while the drawer sees the draft behind Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: a head nobody could name is not behind, so take latest is not offered without one Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: the flow drawer's head is the version its payload came from, and its callback type says so Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: a NUL in a move's summary is dropped, and take latest simply adopts the head it was handed Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: a loaded raw-app draft keeps its own fork base, and an unknown head is refused Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: a routed discard names where it landed, a superseded drawer opening is dropped, and a loaded draft keeps its base in every editor Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: a legacy draft occupies its destination, a superseded opening writes nothing, and a loaded flow draft keeps no base it lacks Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: the drawer owns its opening, a loaded script draft keeps no base it lacks, and a legacy occupant says who can clear it Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: taking the diff drawer without a token claims it, and the classic app editor takes one Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: a retried routed discard still names the destination, and filling the drawer takes the opening too Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: a no-op routed discard names the destination only to someone who could write there Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: the no-op routed discard gates its answer on reading the destination, and a session draft keeps its unknown base Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: abandoning an opening clears the drawer it still owns Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: an app deploy pins only a version it wrote as the next draft's base Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: the deploy-override diff takes an opening its editor can hand back Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor: pin the version this deploy wrote even when one landed on top, and tighten three comment blocks Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: a deploy claims only the version it appended to the head it read, and names the head separately Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: a deploy always names the head it left behind, and pins a base only when it can claim one Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: the derived base is read after the sanitizer, and a deploy that claims nothing leaves no base to compare Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: the route's lineage follows an in-place deploy, and the raw-app editor's event type carries the head Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: the raw-app deploy comment says what that editor actually does with version Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: a group member can be told where their item went, and a deploy names the head's author Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: an emptied selection is no shift anchor, and a deploy leaves no draft for the prompt to compare Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: session tabs compare the same base pair, and a consumed draft is not out of date Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: a failed anchor read is not a raced deploy, and take latest closes only its own drawer Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: an unclaimed deploy always confirms, and the prompt keeps warning a loaded teammate draft Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: the base-unknown confirmation says what it knows, and two comments match the guard Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: the other app kind collides whoever owns it, and session tabs get a head to compare Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: the cross-kind refusal reads properly, and a session flow keeps its own response's head Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: a fresh session checkout takes the head its payload came from, and a deploy keeps the base it pinned Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: the move endpoint validates its source path, and two comments say what their branch does Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: an unanswered head read confirms rather than assuming the app editor is current Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: a deploy is not blocked by the draft a move carried to its destination Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: an unread head confirms with the copy for caution, not for an observed deploy Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: the move record alone excuses a carried draft at the destination, whoever owns it Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: an app deploy answers with the version it wrote, so the editor stops inferring it Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test: the rename assertion reads the deploy's json answer Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: the unread-head warning reads as caution in the deploy drawer too, and the cross-kind refusal names a remedy Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: a reused destination retires the routes pointing at it, and draft_base stays out of diffs Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: the app head is the tail of app.versions, not the newest timestamp Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: app history lists in deployed order, so the picker numbers it right Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: the ordering test's setup sql compiles offline, and the head join names its app Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: kinds that cannot move skip the move lookup, and the move wording needs read Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * perf: a deploy history comes a page at a time, so the diff drawer opens at once Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: a history stays whole unless asked to page, and pages inside the version array Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: an asked-for history page is bounded, and a failed one is not the end of the list Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: an unasked history is whole again, and an absurd page is empty not an error Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: naming only a page still asks for one, and a stray version stays reachable Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: a fork's nul-poisoned draft arrives clean, so its dangling identity repoints too Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: a raw app names its deployed version even when the history will not load Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Ruben Fiszel <ruben@windmill.dev> |
||
|
|
01b38320ea |
chore(main): release 1.815.0 (#11212)
* chore(main): release 1.815.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> |
||
|
|
37e493ae66 |
feat: add an instance setting to refuse a token in MCP URLs (#11162)
* feat: add an instance setting to refuse a token in MCP URLs
MCP clients are commonly configured with the token in the URL
(`/api/mcp/w/{workspace}/mcp?token=...`). A URL-borne credential ends up in
browser history, proxy logs and referrers, so an instance can now turn that
channel off with the `mcp_disable_token_query_param` global setting and leave
the Authorization header as the only way in, which sends MCP clients through
the OAuth flow the endpoints already advertise.
The rejection is a middleware on both the workspaced and the gateway MCP
mounts, layered outside everything that reads a token and inside the
WWW-Authenticate layer, so the 401 carries the resource pointer a client needs
to start OAuth discovery.
Off by default. With it on, the token drawer and the home connect drawer stop
offering to mint a token for an MCP URL and hand over the bare URL instead.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix: read the MCP URL policy when a URL is asked for, and drop the all-workspaces option
Two review findings on the token drawer:
The policy was read once per page load and cached for the browser session, so a
superadmin turning the setting on left every open tab handing out `?token=` URLs
the server now refuses. Both entry points now read it when the user actually asks
for an MCP URL: when MCP mode is entered, and when the connect drawer opens.
The workspace picker offered "All workspaces / Multi-workspace", but the gateway's
consent screen binds the token it issues to the one workspace picked there, so
OAuth has no multi-workspace grant to hand out. That entry is now token-only.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix: don't guess the MCP URL policy, and say where the switch lands on restart
Review findings:
The comment on the settings load claimed `MODE=mcp` as the target deployment, but
that mode joins no monitor loop, so the startup pass is its only read and a change
lands on restart. That is true of every global setting there, `base_url` included;
the comment now says so, and the setting description tells an operator running
dedicated MCP servers what to expect.
A failed settings probe resolved to "tokens allowed", so with the switch on the
drawer would mint a non-expiring token and hand over a URL the server refuses for
as long as it exists. The probe now propagates its error and the panel reports it
with a retry, creating nothing until the answer is known.
The test passed a valid token, so it could not tell a rejection before
authentication from one after it. It now also sends a token that was never valid
and asserts the middleware's own message, which fails if the layer moves inward.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix: withhold the MCP URL until a workspace is picked
With no persisted workspace the store starts undefined, so opening the drawer from
/user/workspaces before choosing one rendered a copyable
`/api/mcp/w/undefined/mcp`. It reads like a real URL and a client pointed at it
would never connect. The panel now asks for a workspace instead, matching the guard
the token branch already has on its generate button.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: drop the coverage-status note from the MCP switch test
It documented what the test does not reach rather than a constraint the next
reader could break; that belongs in the PR, not the module doc. The layer-order
rationale, which is what a future edit would break, stays.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* refactor: fall back to the bare MCP URL instead of alerting on a failed read
When the setting read fails, show the bare URL rather than an error with a retry.
It works whichever way the setting is, so no alert is needed, and it still never
mints a token for a URL the server may refuse. The connect drawer's wording falls
back the same way so the blurb matches the panel.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* refactor: move the MCP URL token setting to Core
It sat in the Auth/OAuth/SAML list, which the settings sidebar shows under SSO,
suggesting a dependency on SSO that does not exist: MCP OAuth has Windmill act as
the authorization server, and any login method, password included, completes it.
It is an instance-wide credential policy, so it now lives with the other ones in
Core, kept out of quick setup like its neighbours.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
9d335de87a |
feat: add a workspace toggle that adds its admins and developers to new forks (#11215)
* feat: add a workspace toggle that adds its admins and developers to new forks Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: add members copied into a fork as manual members, not instance-group ones Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * style: keep the fork members copy comment at the query and drop the raw spacer Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
9320312eac |
feat: cap user token expiration with an instance setting (#11159)
* feat: cap user token expiration with an instance setting Adds `max_token_expiration_days`, an instance-wide ceiling on how far ahead a token created through `POST /users/tokens/create` may expire. With it set, that route refuses a token with no expiration and one that expires past the window; absent or non-positive, nothing changes. Only the user-facing handler enforces it. Server-side mints (native trigger webhook tokens, app embed tokens, sessions) pick a lifetime the caller never chooses and go straight to `create_token_internal`, so they stay uncapped, as does the superadmin `impersonate` route. Service accounts are exempt, in the workspace the token targets or in any workspace for a global token, so unattended automation can keep longer-lived credentials. The token form now surfaces the API error instead of only logging it, and offers "Expires In" in MCP mode as well: that mode always sent no expiration, which the cap refuses, leaving MCP URLs impossible to generate. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: shorten over-long token expirations instead of refusing them Refusing a non-compliant request breaks the callers that cannot comply. The CLI authorization page, `wmill user create-token` and the editor's language-server token each pick a lifetime — usually none at all — with no way to read the setting, so a cap made browser login hang and the editor lose its LSP root rather than stopping the long-lived tokens the setting is aimed at. `cap_token_expiration` now returns the expiration to store, shortening a request that asks for too long or for none. The policy still holds absolutely, no caller can break, and there is no clock-skew boundary where an expiration exactly at the ceiling flips to an error. The token form needed no changes at all, so its MCP and error-toast edits are gone with it. Also drops the Enterprise badge on the setting, which nothing enforced, notes the mint paths in docs/auth-surface.md, and pins that `tokens/impersonate` and the second-workspace case stay outside the exemption. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: drop the unintended token-form change and correct the exemption docs The token form needed no change once the ceiling shortens rather than refuses, but the earlier revert restored from the index, which already held the staged edit, so the MCP expiration field and the error toast stayed on the branch with a comment justifying them by a refusal that no longer happens. docs/auth-surface.md claimed a service-account row in any workspace exempts outright; that only holds for a workspace-less token, which has no workspace to match. A ceiling written as a string, which the YAML instance config and config sync can both produce, now has a test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat: offer only expirations within the ceiling in the token form The server shortens a token that asks for longer than `max_token_expiration_days` or for no expiration, which the token form could not tell anyone: a user picking "No expiration" got the ceiling silently. The form now reads the setting and, with one set, drops "No expiration" and every choice above it, adds the ceiling itself as "N days (maximum)" and selects it, and says the instance limits tokens to N days. MCP mode hides the expiration field and always sent none, so with a ceiling the field now shows there too and keeps its value across the toggle. Without a ceiling the form is unchanged. Reading it needs no superadmin: the setting joins the keys any logged-in user can read through `GET /settings/global/{key}`. It holds a policy, not a secret. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: make the token form and the server agree on what counts as a ceiling The form parsed `max_token_expiration_days` more loosely than `cap_token_expiration`, so the two could disagree on whether a ceiling exists at all. A `7.0` from the YAML instance config, or a string such as "7.0" or "1e1", made the form hide "No expiration" and announce a 7-day limit while the server capped nothing; a value between chrono's and JavaScript's date limits preselected an expiration the server could not parse. Both now read the same thing as a ceiling: a whole number of days from 1 to 1,000,000, stored as an integer, an integral float or a string of digits. `parseMaxTokenExpirationDays` holds the frontend's copy, and the instance settings validation uses it too, so the settings page no longer accepts a value the server would ignore. The bound replaces the date-range guard on both sides. Also corrects the rationale for shortening rather than refusing: the setting is now readable by any logged-in user, so those callers do not read it rather than cannot, and CLIs already installed never will. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: cap service-account tokens like everyone else's The ticket exempted service accounts from `max_token_expiration_days`, but their tokens are the long-lived ones a rotation policy is meant to bound, and the exemption let any workspace admin get an uncapped token by impersonating one. It also left the token form unable to agree with the server: an admin impersonating a service account was offered only capped choices while the server would have kept any. `cap_token_expiration` now takes just the requested expiration, with no per-caller lookup, and the service-account query and its cache entry are gone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: cap superadmin impersonation tokens and pin the frontend parser `POST /users/tokens/impersonate` wrote its own token row with whatever expiration the superadmin sent, so it was the one route left that could mint a token that never expires with `max_token_expiration_days` set. The ceiling only decides the stored expiration (the auth lookup never reads the setting), so leaving it uncapped meant exactly that. It now goes through `cap_token_expiration` like `create_token`; nothing in Windmill calls it, so no caller changes. Also adds `tokenExpiration.test.ts`, pinning which stored values `parseMaxTokenExpirationDays` reads as a ceiling against the server's reading, and documents that tokens existing when the setting is turned on or lowered keep their expiration. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: reject a max_token_expiration_days the token routes cannot read The settings API and config sync stored any value for the key, and the token routes can only read an unparseable one as no ceiling. A typo such as `7.5` or "7.0" was accepted and silently turned the policy off. `parse_max_token_expiration_days` in windmill-common is now the single server reading of the setting: null or empty clears it, a whole number of days within the bound is the ceiling, anything else is an error. The settings write hook and `sync_global_settings_declarative` reject that error, and `cap_token_expiration` reads through the same function, logging a value written around both. Tests: the parser's accept/clear/reject table (the same table as the frontend parser's), the settings API refusing 7.5, and config sync refusing "7.0". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: reserve the CLI login token label so its expiry does not email the user With a token expiration ceiling, the token the CLI authorization page mints now expires, so every `wmill` login earned an "expiring soon" and an "expired and deleted" email and critical alert. The CLI already signs in again on its own when that token stops working, so those notifications ask the user to do nothing. The page now labels it `cli-login:<username>` (previously `cli-<username>`), reserved in `is_user_token` and its SQL and Svelte mirrors: no expiry notifications, and the label cannot be edited. A colon-terminated namespace like `embed_app:` and `impersonation:` keeps hand-made labels clear of it. Not in `is_server_minted_label`, since the page mints through `/users/tokens/create`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: skip the expiring-soon warning for tokens that were short-lived from the start A token whose whole lifetime fits in the 7-day warning window got its "expiring soon" email (and critical alert, when enabled) minutes after it was created, about a lifetime its creator had just picked. With an expiration ceiling of 7 days or less that is every token created from the form or `wmill token create`. `register_token_expiry_notification` no longer queues a warning for such a token. The window is now `TOKEN_EXPIRY_WARNING_DAYS`, shared with `check_expiring_tokens`, so shortening the warning window can never leave tokens of an intermediate lifetime with no warning at all. The "expired and deleted" notice still goes out for every user token. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: exempt service-account tokens from the expiration ceiling again Service accounts are the identity automation that needs a long-lived credential runs as, so their tokens are exempt from `max_token_expiration_days` once more: a service account in the workspace the token names, or in any workspace for a workspace-less token. `tokens/impersonate` checks the impersonated account, so a superadmin minting a token for a service account gets the same exemption. The token form applies the same rule for the account it is running as, when that account is a service account in the current workspace, which is what an admin impersonating one sees; otherwise it would offer only capped choices while the server keeps any. Any workspace admin can create and impersonate a service account to hold an uncapped token, so the ceiling bounds personal tokens; the doc comment and docs/auth-surface.md say so. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: decide the token form's service-account exemption from the token's workspace The form treated the account as a service account only when it was one in the workspace the app was on, while the server checks the workspace the token is for, or any workspace for a workspace-less token. With an email that is a service account in one workspace and an ordinary member of another, picking the other workspace in MCP mode offered "No expiration" and the server silently stored the ceiling; the reverse hid the exemption. `GET /workspaces/users` now returns each membership's `is_service_account` (its query already joins the `usr` row), and the form applies the server's rule to the token's own workspace. The selection becomes a derived value held within the ceiling, so switching to a capped workspace never leaves an unoffered choice selected. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
5639187fec |
feat(auth): 2 h login links and a click-to-sign-in page for emailed ones (#11203)
* feat(auth): 2 h login links and a click-to-sign-in page for emailed ones
Raise the login link cap from 15 min to 2 h, so a link sent by email still
works when it is read.
A link minted with `confirm: true` is a /user/login_link page instead of
the API path. Loading the page does nothing; its button POSTs to
/api/auth/login_link/{token}, which spends the link and answers where to go.
Mail scanners that open links on delivery no longer burn them. Links minted
without `confirm` still sign in on open.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(auth): keep the login link page to design-system components
A tokenless visit bounced off a raw <p>; send it to the page a spent link
already bounces to, and show the modal's own spinner while it goes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
0e807fb1dd |
feat: put a data table's connection under Postgres roles (#11020)
* feat(datatables): put a data table's connection under Postgres roles
A data table backed by the instance database resolved to exactly one Postgres connection,
`custom_instance_user`, for everyone who could reach it at all. There was no way to say
this job reads, that one writes, this one never sees the salaries table.
A data table role is now a real Postgres login on the cluster, defined once for the
instance by a superadmin and named exactly as they named it. A script that declares
`-- role analytics` connects as `analytics`, and Postgres decides what it may touch —
grants are ordinary SQL. Windmill answers only "may this caller ask for this role", from
the tenant lists on the data table entry: `u/alice`, `g/analysts`, `f/finance` or `*`.
A data table with no `permissions` block behaves exactly as before.
Everything that opens a connection on someone's behalf goes through one chokepoint,
`get_datatable_resource_from_db`, which takes the identity explicitly and fails closed when
there is none. The role logs in as itself — never `SET ROLE`, which a script could
`RESET ROLE` its way out of.
A fork's data table entry becomes a pointer at the workspace that governs it rather than a
copy of it. The settings clone used to hand a fork a byte-identical entry naming the
parent's database, which a fork admin could edit to grant themselves `admin` there; a
pointer has nothing local to edit, and its tenants are evaluated as a member of the
governing workspace, by email. `permissions` is stripped from the workspace export and
ignored on import: tenants name principals of one workspace, and a settings push is not
where an access decision should be made.
Operations that see the whole database whatever the roles grant stay with the governing
workspace's admins: editing the roles, a migration that declares none, and opening a
replication stream for a Postgres trigger or capture.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR
* fix(datatables): gate the paths that reach a whole database as admin
Auditing what still resolved through the unchecked resolver turned up three that act for a
caller and hand back the admin connection: `resolve_pg_source_checked` (behind schema
export, the full-schema read, database creation, import and the forked-database drop), the
connection test, and the schema snapshot a fork clone takes of its parent. On a data table
under roles each let any workspace member — or a fork admin who is nobody in the governing
workspace — read or copy the whole database whatever its roles grant.
All three now require admin reach on the governing workspace. A dump taken under a
restricted role would be a silently truncated copy rather than an error, so refusing is the
only right answer for the copy paths.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR
* fix(datatables): confine roles to the instance database, and stop a fork reaching the parent's bookkeeping
A data table role is a login on Windmill's own Postgres. Nothing stopped a workspace admin
putting a *resource-backed* data table under roles, at which point the executor dialled the
host that resource names — one the admin chose — with the role's real cluster password, and
`CONNECT` is granted to every registered instance database. Both ends now refuse: the
permissions endpoint rejects the save, and the chokepoint refuses to substitute credentials
on a non-instance entry rather than trusting the record it read.
Two more places reached the governing database without answering to it. The initial-migration
generator returned a `pg_dump` of the whole schema to any member. And the migration
rename/delete cascade followed a fork's pointer into the parent, so a fork admin renaming or
removing their own local entry relabelled or wiped the parent's `_wm_migrations` — after
which the parent re-runs every migration from zero. The remote half is now skipped when the
entry resolves into another workspace, which is also just correct: a fork renaming what it
calls a data table changes nothing about the data table.
Also: revoking a tenant now bounces the replication streams of every workspace holding an
entry that resolves here, not only the governing one, so a fork's trigger stops rather than
living on inside its open connection; the instance role catalog and the governing workspace's
tenant lists are no longer returned to someone who cannot edit them; and the tenant rename
dedup collapses non-adjacent duplicates, per role rather than once any role changed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR
* fix(datatables): fail loudly where a role or a pointer can be left half-recorded
Three ways the feature could end up in a state nobody could see or undo.
Creating a role writes the cluster first and the catalog second, but the catalog write was an
`UPDATE` that matched nothing when the instance Postgres settings row was absent — leaving a
live login with a password nobody recorded: invisible to the catalog, un-recreatable because
the name is taken, and un-deletable because there is no entry to delete. It now errors, so
the operation is retryable once the row is restored.
Deleting a workspace only nulls the fork lineage; the data table entries pointing at it are
left resolving to nothing. Sweeping them is not an option — turning a pointer back into a copy
would hand each fork the database outright — so the delete now names the data tables it
stranded, and resolving one says which workspace is missing rather than reporting a data table
this workspace never had.
`InstanceDatatableRole` derived `Debug` while holding a Postgres password; it is now
hand-written so `{:?}` on the catalog cannot put a live credential in a log line.
Adds the two branches the reviews found unpinned: a caller who is not a member of the
governing workspace at all, and `NoIdentity` — the compatibility path for an agent worker that
predates this and sends no job id.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR
* fix(datatables): unbreak two operator messages and two comments that described other code
The two strings this branch added for states an operator hits once — the catalog write that
matched nothing, and the delete that stranded a pointer — were collapsed from their multi-line
form with the indentation left in, so both rendered with a fourteen-space gap mid-sentence.
`list_datatables` claimed to report a chain it cannot follow and then dropped it; it does drop
it, and the comment now says why that is the right place to stay quiet. The non-superadmin
check in `edit_datatable_config` was introduced as also covering references, which it does not
and need not: `reference` is overwritten from the stored entry for every caller before the
check runs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR
* fix(datatables): serialize role catalog mutations, and state each helper's authorization contract
The catalog is one JSON document, so create, rename, enable and delete are all
read-modify-write. Two concurrent creates read the same snapshot, both succeed in the
cluster, and the second write drops the first — leaving a live Postgres login with a password
nobody recorded, which is the exact state the delete path exists to prevent. Every mutation
now runs in one transaction holding an advisory lock across the read, the cluster DDL and the
write, so a lost update cannot happen and a failure rolls the whole thing back. The DDL
helpers take that transaction rather than the pool, which is what makes the lock cover them.
Their statements moved off `sqlx::raw_sql`: the simple protocol is only needed for genuinely
multi-statement SQL, and its future is not `Send`, which an axum handler holding the
transaction requires. Each of these is one statement anyway.
The new cross-crate surface now says what callers must do. `read_role_catalog` returns
plaintext credentials; `create`/`rename`/`set_login`/`drop_instance_role` and
`converge_connect_grants` mutate cluster-wide state; `read_datatable_entry` reads a workspace's
raw config. All of them are superadmin-gated by their current handlers, but nothing said so at
the definition, which is where the next caller looks.
Also: the roles table reloads after a failed login toggle instead of leaving it claiming a flip
that did not land; the rename affordance is the design-system `Button`, not a raw one; and
`resolve_datatable_pg_as_caller` drops a `role` parameter no caller ever filled — browsing
resolves as the data table's default until the database manager grows a picker.
Why role passwords stay a plain `String` while the instance user's password beside them is a
`StringOrSecretRef`, asked three times across reviews: that one is a secret ref because an
operator supplies it and may want it from their own backend, while these are minted here and
never entered by anyone, so there is nothing for a ref to point at. Encrypting generated
secrets at rest is a separate change that would take the replication password with it. Now
said at the field.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR
* fix(datatables): give the role catalog its own row, out of reach of the config machinery
Putting it inside `custom_instance_pg_databases` was the wrong call, and it cost two ways.
The catalog serializes a generated Postgres password per role, and that row is the
operator-facing instance config, so the passwords reached `get_instance_config` and its YAML
editor — a live cluster credential in a response body, a UI field and any log of either.
Worse in the other direction: `to_settings_map` strips the catalog, so a full-row upsert of
that key writes the row back without it and the catalog is gone, while the cluster keeps every
login it described.
`custom_instance_replication_pwd` is the precedent and says exactly why — a generated secret,
written only by the server, never operator-authored, hidden so the config machinery cannot
read, rewrite or drop it. The catalog is the same thing, so it now has the same shape:
`datatable_roles`, in `HIDDEN_SETTINGS`, `PROTECTED_SETTINGS` and the agent-worker denylist.
No redaction to keep in step with three code paths, and no way for a neighbouring write to
take it out.
Two races on the same shared documents. `edit_datatable_config` read the stored data tables
outside its transaction and then wrote the whole `datatable` document, so a permissions save
committing in between was silently rolled back; it now reads under `FOR UPDATE`. And
`set_datatable_permissions` validated role ids against the catalog before opening its
transaction, so a deletion in between let it write a deleted role back — including as the
default, which every later job then fails on; it now holds the catalog lock and the settings
row across validation and write.
Completes the authorization contracts the previous commit claimed but did not finish:
`read_datatable_entry` (which it named and missed), `resolve_governing_datatable`, whose whole
job is to answer for a workspace the caller may not belong to, and
`converge_connect_grants_with`, which had not inherited its wrapper's.
Also the generic Python SDK reference: `_format_py_params` learned the bare `*` last time, but
`extract_py_functions` is a second formatter and still rendered `datatable(name, role)`, so
code written from that page passed a keyword-only argument positionally.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR
* fix(datatables): make the concurrency test pin the handlers, and the contracts describe what is enforced
The concurrency test reimplemented the read-modify-write inline, so deleting the lock from all
three handlers left it green — it pinned Postgres, not the code it was written for. It now
drives `create_datatable_role` twice concurrently and asserts the catalog kept both names.
Checked the way the last one should have been: removing the lock from the handler makes it
fail with "wmtest_a_… is a live cluster login the catalog forgot".
The contracts added last commit were stricter than this PR's own callers, which is worse than
none — the next reader sees a rule already broken and learns to ignore it.
`read_role_catalog` said superadmin-only while two of its four callers are open to any
workspace member, and `converge_connect_grants` said superadmin while
`set_datatable_permissions` reaches it as a workspace admin. Both were fine on substance: the
rule that actually holds is about the credential never reaching a response, log, audit record
or export, not about who may call. They now say that. `read_datatable_entry` gets the same
treatment rather than the one the earlier message claimed for it: it is the primitive every
resolution goes through, so it is deliberately open, and what must not escape is `permissions`
— it names the governing workspace's users, groups and folders.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR
* fix(datatables): close the last ways a role or a pointer can be left pointing at nothing
The raw settings readers hand back whatever is in the row, so moving the catalog into its own
`global_settings` key protected the config machinery and left `GET /settings/global/datatable_roles`
and the settings listing returning every live password. Both now filter that one key. The
neighbouring `custom_instance_replication_pwd` has the same shape and is not touched here: it
predates this and widening the fix to it is a decision about an operator workflow, not a
consequence of this change.
Three ways a save could leave something resolving to nothing:
A permissioned data table could be moved to a PostgreSQL resource. The block was carried across
as a server-owned field, the runtime refuses roles on a resource-backed table, so the save
succeeded and every job afterwards failed. Refused instead — turning roles off first is one step,
and it keeps discarding an access decision something somebody chose.
Renaming a governing data table left every fork pointing at the old name: the data table
disappears from their pickers and their jobs stop, with nothing in the renaming workspace to
suggest why. The rename now follows into the pointers in the same transaction.
Deleting one cannot be followed the same way, so it is reported instead — the response names what
it stranded, the way deleting a workspace does, and the fork's own error already says which
workspace is gone.
Also: `ensure_instance_db_grant_options_unchecked` claimed superadmin while the permissions
handler reaches it as a workspace admin (the same class fixed last commit, one instance missed);
the role entry kept an `instance_config_schema` derive it no longer needs; `write_role_catalog`
was the one writer of that table not stamping `updated_at`; and the concurrency test dropped its
roles only on success — a failing run is exactly the one that creates them without recording them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR
* refactor(datatables): put the role catalog in its own table, not in global_settings
Five findings across three rounds were all the same choice. A set of live Postgres credentials
was living in `global_settings`, which has generic read, list, write, config-export and CLI
round-trip paths that know nothing about what they carry: the passwords reached the instance
config and its YAML editor, a full-row upsert of a neighbouring key erased the catalog,
`GET /settings/global/{key}` and the settings listing returned them raw, and this round the
redaction that fixed the last two turned `wmill instance push` into something that wipes every
password — a fix breaking the assumption the previous fix made. `POST /settings/global/datatable_roles`
could also empty it outside the lock.
The approved plan offered a table or `global_settings`, so this is the other option it already
allowed rather than a new design. `datatable_role` is a table: no generic settings path can read
it, list it, export it, write it or round-trip it, so none of the five needs a guard. The
redaction, the hidden/protected/agent-denylist entries and the JSON document all go with it.
One row per role also removes the read-modify-write the concurrency work was about: two
concurrent creates are two inserts, and the unique index on `name` is what settles a collision.
The advisory lock stays for the one window rows do not cover — `CREATE ROLE` is invisible to
another transaction until commit, so without it both creates pass their `pg_roles` check.
Also from this round: rename mappings are checked against the configuration they claim to
describe, since fork pointers are rewritten from them — a caller could otherwise submit
`main -> missing` against an unchanged config and repoint every fork of `main` at a name nothing
has, and `A -> B` plus `B -> C` moved what pointed at `A` all the way to `C`. And the warning
naming forks a delete stranded reached the response but not the screen: both the data table
settings save and the workspace delete now show it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR
* fix(datatables): validate a rename against the save it describes, and re-check under the locks
Three from the round, all about deciding on state that could already have moved.
A permission save resolved the data table and checked it was instance-backed before taking any
lock, then wrote under one. A config save committing in between could move the table onto a
PostgreSQL resource — recreating exactly what the transition guard refuses — or rename it, in
which case the write targeted a key that no longer existed and reported success having changed
nothing. It now re-resolves and re-checks on the locked state.
Rename validation checked that the source existed before and the target existed after, which
still accepts `main -> decoy` against a save that keeps both: every fork of `main` then follows
onto a different data table, silently, because it keeps resolving. The rule is now the actual
old-to-new key transition — a source may only survive if another rename took its name, and a
target may only pre-exist if another rename freed it. That also stops two sources sharing one
target, and it admits a swap, which the previous guard refused: `datatables` is keyed by name, so
a swap cannot be done one save at a time, and refusing it was a regression against main. The
pointer cascade now runs in two passes through a temporary name, the way the migration cascade
one layer down already handles the same shape, so `A -> B` with `B -> C` moves each pointer once
from what it named before the save.
The tenant mutators say what they are for: they write an access decision for any workspace named,
with an arbitrary mutation, and exist for the transaction that frees or renames a principal.
Editing a decision on purpose belongs in the permissions endpoint.
Carried in the same change: the stranded-fork list is a field rather than a phrase to grep out of
a success string; the pointer cascade matches with `EXISTS` instead of a `LIKE` over the whole
document, so a workspace whose pointers name something else is not rewritten to a byte-identical
value under an exclusive lock; and `InstanceDatatableRole` drops the serde derives left over from
the JSON document, one of which would emit `pwd`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR
* fix(datatables): cascade on the leave route that is used, gate migrations before the admin connection, and drop a role atomically
The tenant cascade on leaving went onto `/users/leave`. The UI and the generated client call
`/workspaces/leave` — a different handler in a different crate with the same name — which
deleted the membership and left `u/<username>` in the tenant lists. Leaving and rejoining
therefore restored the access the leave was supposed to end, and a later account taking the
username would have inherited it. The regression test drives the route the client actually
calls; without the fix it fails with "leaving kept the tenant".
The migration endpoints authorized too late. `run_datatable_migrations` opened the data table's
admin connection, created `_wm_migrations` and read it before reaching the per-migration role
check — so with nothing pending, nothing was checked at all. Rollback returned before its check
when nothing was applied, and the status endpoint had none. All three now ask, before any
connection is opened, whether the caller can reach the data table as any role at all; which role
a given migration runs as is still decided per migration, and by the executor after that.
Deleting a role committed the cluster drop and the catalog row, then swept the tenant lists in
separate transactions. A sweep failing part-way left workspaces naming a role nothing can connect
as, while the retry answered `NotFound` because the catalog entry was already gone. The sweep now
runs in the same transaction, so the drop, the row and every tenant list commit together.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR
* fix(datatables): refuse to copy a data table that is under roles
pg_dump carries no roles and the import runs with --no-privileges, so a copied
data table arrives owned by the admin connection with no GRANT for any role.
The settings clone brings `permissions` across, so the fork's tenants pass
Windmill's check, connect as the role they were given, and are denied by
Postgres on everything: an entry that reads as configured and answers nothing.
Refuse the copy — in the import endpoint before any data moves, and in the fork
path the CLI takes. Replaying the source's owners and ACLs into the clone is
what lifts this, and is a change of its own. Dropping `permissions` from the
copy instead would be the unsafe half, since the copy holds the parent's rows.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR
* fix(datatables): refuse the clone's database too, not only its data
A clone is two endpoints: `create_pg_database` then `import_pg_database`. Only
the second refused a data table under roles, so a fork asking to clone one
created and registered an empty `wm_fork_…` instance database and then failed —
and nothing collects it, since `drop_forked_datatable_databases` only drops
entries carrying `forked_from` and no entry names this one.
Refuse in both, so the clone stops before a database exists.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR
* nit worker error msg
* fix pg_dump stuck on version 17 on nix
* fix(datatables): refuse a malformed role annotation instead of ignoring it
`-- Role operator`, `-- role operator;` and `-- role operator -- why` all failed
the annotation parser's exact-match rule, so the query fell through to the data
table's default role and ran, silently, under a login the author did not choose.
Naming a role exists precisely to not do that.
A leading comment whose first word is `role` is now an annotation attempt: the
keyword matches case-insensitively, one trailing `;` is tolerated, and anything
else is an error naming the line. Only callers that already know the target is a
`datatable://` reference ever run this, so ordinary SQL keeps its comments.
Also bumps the dev shell's postgres client to 18 — it trailed the server the dev
database runs, which takes out every data table export, clone and fork-with-data.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR
* fix(datatables): refuse a malformed role query string instead of ignoring it
`?Role=analytics`, `?role=` and `?x=1&role=…` all fell through the reference
parser's exact-match rule, so the connection resolved to the data table's default
role and ran under a login the caller never asked for — the URI half of the same
trap as a malformed `-- role` annotation.
The key now matches case-insensitively, and anything else in the query string is
an error naming it; `role` is the only parameter a reference takes. Callers that
only need the entry keep a lenient `datatable_ref_name`, since they never act on
the role. The DuckDB `ATTACH` parser propagates it rather than attaching under
the default.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR
* fix(datatables): carry the role annotation into the row_to_json retry
The retry rebuilds its SQL from `pruneComments(code)`, so the leading comment
block never reached the second attempt — and with it the `-- role <name>` line
that decides which login the query runs as. The retry connected as the data
table's default role instead, so a query the first attempt was denied could
succeed on the second, reported as "recovered with the row_to_json fix".
Carry the leading comment block over. The retry itself is unchanged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BjfMkJyKzodxkobqGZ6Lqb
* chore(datatables): don't mount the roles UI until the ACL editor lands
Enforcement ships first. The permissions drawer is what turns roles on, and the
catalog section is what creates them — both are only useful once there is a way
to grant a role the privileges it needs, which arrives with the ACL editor. Left
mounted they would offer a feature whose other half does not exist.
The two components are complete and reviewed; only their call sites here are
commented out, with a note pointing the follow-up PRs at them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BjfMkJyKzodxkobqGZ6Lqb
* fix(datatables): honour `-- role: x`, and fix the DuckDB attach test
Two review findings, both real.
`attach_datatable_parses_name_and_role` never compiled: `parse_attach_datatable`
returns `Result<Option<_>>` now and one call site kept a single `unwrap`. Its
`?Role=analytics` case also asserted a refusal, contradicting the parser in the
same commit, which matches the key case-insensitively. Replaced with the cases
that are genuinely malformed, and a positive one for the cased key.
`-- role: analytics` fell through to the default role — the silent fallback the
strict parser exists to remove, for the spelling most likely to be typed. The
keyword now accepts an optional colon, attached or spaced, while a word that
merely starts with it (`rolebased`) is still not an attempt.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BjfMkJyKzodxkobqGZ6Lqb
* fix(datatables): clone a fork's pointer instead of failing after the copy
Forking a fork with cloning left an orphan database. The preflight resolves the
pointer and sees the governing entry, so both endpoints ran and filled the new
database; `apply_forked_datatable` then refused the inherited pointer and rolled
the fork back, stranding a registered `wm_fork_*` that no entry names and whose
name blocks the retry.
Refusing earlier would have been the smaller change, but forking a fork and
cloning worked before pointers existed, so it would trade an orphan for a
regression. Resolve what the pointer names and write the terminal entry the
clone needs: the whole `database` object rather than a patch of its
`resource_path`, since a pointer has none, and `reference` removed with it.
Also accepts `-- role=x` and `-- Role = x`, two more spellings that fell through
to the default role.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BjfMkJyKzodxkobqGZ6Lqb
* fix(datatables): refuse to roll back the catalog while roles exist
The down migration dropped the table and left every role behind: live Postgres
logins whose passwords only that table carried, so after a revert Windmill could
neither use, disable nor delete them, and re-applying could not recreate them
because the names were taken. Cleaning up here is not possible either — dropping
a role means reassigning what it owns in every instance database, and a
migration runs in one — so it now refuses while the catalog is non-empty and
says to delete the roles through instance settings, which does the cluster work.
Also enforces the instance-only invariant the resolved-pointer clone relies on
rather than only asserting it in a comment.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BjfMkJyKzodxkobqGZ6Lqb
* refactor(datatables): settle clonability in one place, before anything is created
A clone is three stages a workspace apart — `create_pg_database`, then
`import_pg_database`, then `apply_forked_datatable` inside the fork transaction.
Only the third can roll back, and `CREATE DATABASE` is not transactional, so any
refusal that lives there strands a registered `wm_fork_*` that no entry names
and whose name blocks the retry.
That orphan has now been fixed three times, most recently reintroduced by a
guard added one commit ago. Patching each new refusal into the first endpoint is
not the fix; having two places that can refuse is. `ensure_datatable_is_clonable`
now answers every reason a copy can be refused and returns what it resolved, and
the stage that writes the entry only does the work.
Also takes an ACCESS EXCLUSIVE lock before the rollback guard counts, so a role
created concurrently cannot slip between the check and the drop.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BjfMkJyKzodxkobqGZ6Lqb
* fix(datatables): let a retried clone reclaim its own leftover database
A clone creates its target database one request before it copies into it, and
the fork that would name it is written a request after that. Any failure in
between — a pg_dump error, a bad restore, a dropped connection, the source's
roles changing mid-flow — left a registered `wm_fork_*` that no entry names,
and every retry then failed on its name. This predates data table roles.
`create_pg_database` now reclaims such a leftover before creating: only a
`wm_fork_*` database Windmill registered as a data table database and that no
data table or ducklake entry names, in any workspace, archived ones included.
The drop never terminates connections, so a clone still copying into it makes
the reclaim fail instead of being cut off. It is limited to callers who
administer the source — reaching it is not enough, since on a data table
without roles every member reaches it — and anyone else gets the refusal an
existing database always got.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Revert "fix(datatables): let a retried clone reclaim its own leftover database"
This reverts commit
|
||
|
|
d1a25360b0 |
perf: count completed jobs on the v2_job_completed index with a timeout (#11211)
* perf: count completed jobs on the v2_job_completed index with a timeout Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test: cover the count_jobs tags filter on completed jobs Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
d440ebb520 |
chore(main): release 1.814.0 (#11171)
* chore(main): release 1.814.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> |
||
|
|
a571117f3f |
feat: store mcp tool call, result and reasoning on flow conversation rows (#11176)
* feat: store mcp tool call, result and reasoning on flow conversation rows Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: keep the failure reason and web search citations on tool rows Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: persist a structured answer as its own row and keep reasoning-only rows from closing a turn Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: keep a failed Windmill tool's error on its conversation row Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: let a structured answer row claim its streamed thinking Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: scope a structured answer's claim to its own turn and document the row fields as stored Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: claim a textless assistant row only within the newest turn Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * refactor: store the thinking that led to a tool call on the tool row Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep tool calls in stream order and scope a structured answer to its turn Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: store the files a user message carried as object-storage references on its row Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(chat-sdk): read a message's attachments and build their download URL Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(chat-sdk): carry a loaded user message's attachments in AI SDK metadata Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: store the model's call and what it got back on every tool row Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: pass no extras in the orphaned-conversation test's message insert Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(chat-sdk): keep a stored JSON null tool result instead of the row text Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: label a tool call the turn finished without as not finished Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep a structured answer's streamed call out of the did-not-finish label Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: write a turn's conversation rows in order and describe stored tool calls Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: await the image answer row like the agent loop's other rows Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(chat-sdk): place a row nothing streamed by its sequence, not at the end Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(chat-sdk): place only tool rows by sequence, keep a closing row last Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(flow chat): show a failed tool's error, and no Retry on a running or stopped turn A failed tool card showed the row label in place of the error the row now stores. The live tool result now reports failures, so a failed call briefly flagged a running turn as failed; a stopped turn, whose last row is the failed tool or the cancelled flow's failure, offered Retry too. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
c297ed0052 |
feat: managed memory with an inherited or custom memory id per step (#11118)
* feat: split ai agent memory into agent policy, run memory id and step history * fix: scope string memory ids to workspace and flow, keep nested tool history inputs * chore: update sqlx cache for the flow context query * docs: describe memory id scoping as collision-free rather than isolated * chore: regenerate openflow json after merging main * fix: offer no memory id for legacy manual memory, document linked history inputs * fix: seed provided messages from legacy manual memory and hide its note once set * fix: bypass memory when a provided messages expression evaluates to null * fix: require a user message when provided messages are empty * chore: keep the empty messages comment within the line width * docs: name the history inputs wherever linked steps list their flow-local inputs * docs: keep the memory storage path on one line * feat: managed memory with an inherited or custom memory id per step Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: list a custom memory id in the test run form and name where an inherited one comes from Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: keep memory id out of the add-field menu and drop the memory id telemetry Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: keep legacy auto memory without an id working after an untouched redeploy Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: rename step messages to previous_messages and address review Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * style: rewrap comments and docs lines lengthened by the previous_messages rename Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * refactor: read agent memory as either a legacy shape or the current one Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: name the memory setting in ignored-input notes and keep conversions honest Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: keep a legacy memory count unset on open and read a cleared count as off Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: address review on cleared test history and zero-count memory Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: drop flow-local keys from a linked agent resource before interpolating it Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: keep a linked resource's own inputs as fallbacks and note ignored history on image runs Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: restore the linked agent draft tests and log ignored history on every image run Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: resolve the one-of variant from the value when the selected one leaves the list Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: treat zero-count managed memory as off when enabling chat mode and shorten comments Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: stop requiring user_message in the openflow agent contract when previous messages are the prompt Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
4eab995cf7 |
feat: tell test flow conversations from deployed ones and rename a chat (#11179)
* feat: mark test flow conversations apart from deployed ones and allow renaming a chat Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: keep the conversation kind across refreshes and reject NUL titles Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: ignore conversation lists for a kind no longer selected Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: start a fresh conversation listing when the kind changes on a later page Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: refuse sending into a conversation of the other kind Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: refuse cross-kind conversation continuations on the server Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep the conversation filter unavailable while an answer runs Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
23c24a9688 |
feat: collect flow conversations and agent memory once their last message goes (#11178)
* feat: collect flow conversations and agent memory when their last message goes * fix: lock the conversation lookup so a new turn orders against its cleanup * fix: let concurrent turns recreate a collected conversation without conflicting |
||
|
|
9d348f84c7 |
fix: skip expiry notifications for app embed and SDK tokens (#11169)
* fix: skip expiry notifications for app embed and SDK tokens Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * refactor: share app token label prefixes between mint sites and the check Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: skip expiry alerts for impersonation and test-connection tokens Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
3d08197182 |
feat: badge chat-input flows on the home list (#11164)
* feat: badge chat-input flows on the home list Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: keep a malformed draft value from aborting the runnables list Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: only a JSON boolean marks a draft flow as chat-enabled Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
c3e11bd223 |
chore(main): release 1.813.0 (#11141)
* chore(main): release 1.813.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> |
||
|
|
57a99f66a8 |
feat: rename saved agents from the agent editor and flag broken links (#11147)
* feat: list flows that link a saved agent and flag broken agent links * feat: rename saved agents from the agent editor and repoint the flow * fix: show an unreadable linked agent as not accessible, not missing * fix: address review nits on agent rename and missing-agent state * fix: open content search above modals and keep Escape for it * fix: register content search on the opener's overlay stack * docs: scope the global search z-index comment to the bases it clears * refactor: show linked agents' rename warning as for scripts and flows * fix: keep the failed-lookup rename warning to resources |
||
|
|
a48ae656ae |
feat: delete a browser's copy of an AI session past its workspace retention (#11156)
* feat: delete a browser's copy of an AI session past its workspace retention Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: tell the AI session retention only to a member who can reach the workspace Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: keep the retention sweep's design narrative in the docs, not the code * fix: give the session retention its own route, leaving the status contract alone Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: shorten the retention route comment to its constraints * docs: name the two clocks in the retention setting, and the deploy window --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
ee6d317e31 |
feat: retention for AI sessions on the object store and in the browser (#11152)
* feat: retention for AI sessions, swept on the object store and in the browser Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: make the retention sweeps retryable and safe against pushes Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: spare other tabs' sessions, reclaim abandoned split pushes Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: sweep under an exclusive session lock, keep the captured user Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: say a tab selecting a session mid-sweep is not held back Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: sweep local sessions only while no other tab has them loaded Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: one tab sweeps at a time, and keeps the switched user's hold Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test: push the fallback session again before the rotation assertions Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor: keep retention server-side here, move the browser sweep out Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: the retention setting no longer touches browser-local sessions Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
f082fddf41 |
[ee] feat: fall back to instance storage for AI session backups (#11153)
* feat: instance object store as fallback for AI session backups Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: fence the instance store sweep by generation, name it by location Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test: pin that an instance store location tells endpoints apart Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: show the instance storage fallback setting on while it is unset Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: check the generation fence queries at compile time Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: stop the instance storage fallback once the plan is Pro Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
796b6e5297 |
feat: back AI sessions up to the workspace object storage (#11116)
* feat: back AI sessions up to the workspace object storage Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SG5qEPM6Fmf7VerXS5nnWp * fix: bind the backup key to the user and pack pushes within the server caps Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SG5qEPM6Fmf7VerXS5nnWp * fix: keep refused and unavailable marks, one mark per key, stream the flush Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SG5qEPM6Fmf7VerXS5nnWp * fix: settle only fully sent sessions, keep removals while backups are off, cap pull bodies Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SG5qEPM6Fmf7VerXS5nnWp * fix: bound removal marks while backups are off and stale the sync rows instead of dropping them Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: retry a lost lock, cap nested push lists and oversized pieces, drop a stale copy of a chat that outgrew the backup Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: cap pieces per push, size requests in UTF-8, keep a move's removal for an off workspace, disclose the restore counter Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: file a move's removal only once the new copy landed, retire marks through the sync row Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: keep a session marked while deletes are carried over, drop only gone sessions' marks Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: record what a refused flush already stored, stop early when every mark is retired Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: restore past another workspace's removal mark, file a move's removal before its row, bound the first pulled session Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: re-key the backups on workspace key rotation, accept only base64 images, carry a delete on the sync row when its mark cannot be written Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: durable conditional re-key of session backups, re-push on a storage switch Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: fail a push the key rotated under, settle no session split across storages, narrow the re-key module Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: keep a delete filed during a push, bound the pull and re-key listings Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: bound the session listing, mark the store's own user on a write that lands after a user switch Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: list sessions through per-session index markers, hold a session's parts back after a failed one Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: record a rotation on every build, list a session only on the part that completes its push Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: leave an object larger than any push writes unread Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: read each object against its listed size, carry a dirty mark that cannot be written on the sync row Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: note the storages the re-key walk completed on, reach another user's rows on a failed mark, read a head at its cap Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: read the replaced key under its row lock, carry a refused dirty bump on the sync row Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: pull a session that outgrew one answer in pages, imported only whole Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: build a pull page from the smallest keys of the whole listing, stage each page as it lands Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: re-record a key rotated back to, admit earlier-page images, restage over a cut-short restore Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * refactor: delete the backups on key rotation instead of re-keying them Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: end a pull page before an object that grew since the listing, prune what a cut-short restore staged Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: delete the backups before the key commits, skip a planted object whatever its listing says, lock a restore across tabs, prune stale artifact versions Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: keep the backups under a prefix named by the key, delete the previous key's prefix after the commit Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: name the backup prefix by a generation the rotation bumps, never write an older record over a newer one on restore Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: retire a removal only against the storage holding the backup, restart a paged pull whose listing moved Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: fingerprint a pull page before reading it, answer the backup generation apart from the storage identity Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: answer needs_head for a headless session push, prune restaged pieces by id Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: serialize a session's push and removal, open whole pushes with the head, prune only own restores Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: require a head on a whole push, prune before the record lands, restore only under Web Locks Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: incremental pushes ride on a listed session, removals wait for every storage holding a copy Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: a whole push replaces the backup under a per-push token, a pull page is checked after its reads Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: fingerprint a pull page by entity tag and version too Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: a moved session's removal mark names the storages holding the old copy Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: restore a workspace family together, the newest copy of a moved session winning Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: name the marker by the session's move count, abort a family restore a listing failed in Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: list the family again before a restored record lands, require the pull fingerprint Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: list the whole family once per restored workspace, off members included Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: a push split over parts, incremental too, unlists the session until its last part Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: refuse a partial push part that names no push Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: keep a refused bump for a session with no row yet, probe an off workspace again Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: backfill row-carried bumps after a reload, ask an off workspace again on a timer Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: backfill a row for its bumps only when it carries some Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: a backfilled mark that cannot be written counts from the page's counter Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs: say an off workspace is asked again, in the mirror's comments Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * chore: update ee-repo-ref to 1c1dab33563c4907aff8b0da825fb66db60af82a This commit updates the EE repository reference after PR #796 was merged in windmill-ee-private. Previous ee-repo-ref: 289b477ca3fc993da06ec09b11c8f55d5e4e39c1 New ee-repo-ref: 1c1dab33563c4907aff8b0da825fb66db60af82a Automated by sync-ee-ref workflow. * fix: unlist a session while an incremental push changes more than one object Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com> |
||
|
|
781b5a57e8 |
fix(apps): run-mode inline app component uses only pinned content (#11135)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
9fe493311d |
chore(main): release 1.812.0 (#11111)
* chore(main): release 1.812.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> |
||
|
|
42f489685b |
feat: store resource type display names and label hub integrations (#11113)
* feat: label resource types and integrations with hub display names Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: load hub integration names in the app and flow pickers Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: load hub resource type names where drawers title a type Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: store resource type display names and drop the hardcoded list Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: leave display_name out of the fork comparison Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: ignore over-long synced display names, move name loaders Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: share the hub integration list cache, backfill admins only Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep a name over a nameless duplicate, retry failed hub reads Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
69e6efd875 |
fix(git-sync): run auto-pull as the admin who enabled it (#11121)
* fix(git-sync): run auto-pull as the admin who enabled it Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(git-sync): audit the admin grant fork pulls make Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore: bump ee ref for the post-commit fork grant audit Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(git-sync): address review nits on the auto-pull stamp Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore: update ee-repo-ref to ccada062c072d7b74894b63863728fd1ef9bdffd This commit updates the EE repository reference after PR #799 was merged in windmill-ee-private. Previous ee-repo-ref: 7cee30f0cf12721cba551cd754dc817444810470 New ee-repo-ref: ccada062c072d7b74894b63863728fd1ef9bdffd 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> |
||
|
|
57a134e2de |
feat(ai-sessions): share session artifacts with the workspace by link (#11115)
* feat(ai-sessions): share session artifacts with the workspace by link Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pyjp67oR269QAx3b4yf4oH * chore: cache the shared artifact queries for offline sqlx Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pyjp67oR269QAx3b4yf4oH * fix: replace a literal NUL byte in the shared artifact body limit comment Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pyjp67oR269QAx3b4yf4oH * test: pin that a shared artifact is confined to its workspace's path Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pyjp67oR269QAx3b4yf4oH * fix: sanitize shared artifact markdown and validate the artifact id on every route The shared page renders another member's markdown, so ArtifactBody now runs the repo's rehype-raw + rehype-sanitize chain with the chat's link renderer on top; only the session viewer opts into the chat code block (mermaid, apply button). The link renderer keeps a link's text when its href is empty or unsafe, and the scheme check moves to a tested helper. The status route checks artifact_id like share does, so a NUL is a 400 rather than a 500. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pyjp67oR269QAx3b4yf4oH * fix(ai-sessions): say which way re-sharing moves an artifact link The popover offered "Update to v1" when a v2 link was open on a pinned v1, which reads as if v1 were newer. Each direction now has its own sentence and action: a newer version on screen updates the link, an older one shares that version instead, a rename updates the name. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
e8c02c04cd |
feat: windmill-chat sdk for chat-mode flows in external frontends and raw apps (#11117)
* feat: windmill-chat sdk for chat-mode flows in external frontends and raw apps Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018aQiZNAU8g17kWkyTryS5J * fix: keep streamed answers until persisted, finish turns after history fallback Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018aQiZNAU8g17kWkyTryS5J * feat: ai sdk transport and assistant-ui runtime for windmill-chat Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: finish a turn from the flow result until its answer row lands, hash chat ids without crypto.subtle Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: judge a turn answered by a persisted assistant row, wherever it was fetched Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: attribute a turn's answer to its own jobs, keep a local turn when switching conversations Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: mirror local history on every change, attribute failure-handler answers to the turn Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: new chat per token string in the React hook, idle after destroy, no reorder on view Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: recreate the hook's chat on any credential change, namespace local history per user Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: send the latest inputs from the React hook Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
a95e950529 |
feat(cli): list, get and restore trashed items with wmill trash (#11125)
* feat(cli): list, get and restore trashed items from the CLI * docs(cli): tell agents a sync push deletion is restorable with wmill trash * refactor(cli): share the ApiError formatting and type trash flags as integers |
||
|
|
91e6dc39ce |
feat: pre-approved cloud accounts: login links, OAuth adoption, setup, and the trial bridge (#10875)
* feat: single-use login links and oauth-claimable pending accounts * docs: capture the auth surface facts behind login links * fix: accept stringified email_verified from oauth userinfo * docs: describe the oauth claim rule in the auth surface notes * fix: harden login-link redirects and sweep expired links * chore: bump ee-repo-ref * fix: keep expired login links a day so an open still reads as expired * fix: refuse login links for superadmin and devops accounts * fix: re-check the account's roles when a login link is opened * feat: pre-approved cloud accounts finish their setup and start their trial from Windmill * feat: dev-only localStorage opt-in to the cloud UI on localhost * feat: finish-setup entry in the desktop settings menu * style: pulse the settings row while account setup is pending; shorter, blue finish-setup entry * fix: list the configured providers in the finish-setup modal * fix: open the finish-setup modal after the menu has closed * feat: finish-setup provider sign-in keeps the session when the provider asserts another address * chore: pin the EE companion commit * fix: plain toast for the finish-setup refusal * style: format the dev cloud override Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * feat: onboarding skips the source question an invite already answered Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * fix: type the finish-setup icons and login_type as the frontend uses them Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * feat: invited accounts get a workspace name, hub picks and starter prompts from their invite Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * fix: the workspace form reads the invite's name itself, so the picker prefills it too Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * feat: an empty workspace offers the projects its invite picked, one click from importing Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * style: picked projects get identical import buttons Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * feat: a pinned sidebar banner until an invited account has credentials of its own Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * style: the account-setup row speaks the rail's language, tinted not filled Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * chore: pin ee-repo-ref to the import fix Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * refactor: picked projects live in the template picker only; account-setup row moves to the rail footer Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * fix: review round — no portal login for job tokens, finish-setup failures keep the session, prompt labels deduped Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * fix: CI round — trial start is a POST, profile cache follows the session, setup row on MenuButton Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * fix: CI round — no password road where password login is off, cache note on the login form, trial refusal surfaced Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * fix: CI round — set_password guarded on its read, refusal stays on the page, docs and formatting Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * fix: CI round — popup OAuth clears the profile cache, portal helper crate-private, refusal toast stays Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * fix: a refused trial is recorded inline in the rail, not in a day-long toast Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * fix: the refusal notice uses the rail's button and has a collapsed form Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * fix: CI round — SSO can finish account setup, with the same mismatch refusal as OAuth Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * fix: SSO finish-setup rides in RelayState and the refusal notice is a status region Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * fix: keep the finish-setup cookie beside RelayState, hoist the status region, pin session-keyed profile cache Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * fix: empty live region for the trial refusal, drop the setup cookie once adopted, telemetry inventory Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * fix: the trial refusal survives the responsive sidebar swap Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * fix: the trial refusal is shown to the account it answers, modal open prop is required Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * feat: an invited account skips the whole onboarding survey Its source is the invite and its use case was researched before the invite went out, so neither question is asked: the known source is recorded and onboarding opens on naming the workspace. Accounts without an invite profile see the survey exactly as before. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * fix: an invited account with a workspace leaves onboarding before anything paints Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * fix: account-setup state resets on sign-out, onboarding shows a loading state while it settles Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * style: keep the refresh doc comment on refresh Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * fix: profile lists are distinct, and the offer table notes what a users-import does to it Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011HMniEf5hapoKEB6TEBcGy * chore: update ee-repo-ref to 1ba6fe83451f0a1f8fafe04b7187087d51e0f769 This commit updates the EE repository reference after PR #750 was merged in windmill-ee-private. Previous ee-repo-ref: be42722d09832ffff709a1f710f3e97e34d513b2 New ee-repo-ref: 1ba6fe83451f0a1f8fafe04b7187087d51e0f769 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> |
||
|
|
d8d7332eb6 |
feat: add per-route CORS origin allowlist for HTTP triggers (#10833)
* feat: add per-route CORS origin allowlist for HTTP triggers Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: fail closed on cold router cache and invalid origin input Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: resolve CORS route from the decoded path like the request handler Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat: add instance-wide default allowed origins for HTTP routes Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: let non-superadmins read the default allowed origins setting Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat: badge the advanced section when a route's origins are restricted Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: state inherited origins on the control and use one hint row Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: trim the origins tooltip and relabel the toggle when a default exists Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: keep the origins format hint visible until an entry is wrong Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: state the at-least-one requirement in the origins hint Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: import the origins validator in the trigger-http tests Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: make an empty allowlist deny rather than fall back to the default Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: address review nits on origin validation and the CORS editor * fix: derive the origins error from the stored list and tighten host validation * fix: parse real IPv6 hosts and refuse a newly emptied allowlist * refactor: make origin validation advisory except for null and non-ascii * feat: let an empty allowlist be saved as deny every origin * docs: document the empty allowlist as deny every origin * fix: bound allowlists, reject commas, and decide cors after the handler * chore: revert unrelated rustfmt churn in windmill-common tests * chore: revert unrelated rustfmt churn in windmill-common * chore: drop the route types the cors restructure replaced * fix: take the stricter cors decision from before and after the handler * fix: strip runnable cors headers when the routers are unavailable * docs: document the allowlist bounds in the openapi schema * fix: let an unavailable cors read defer to one that resolved * refactor: carry the resolved cors policy from the handler to the middleware * docs: describe why an unavailable read fails closed on the paths that reach it * fix: validate the default origins on the declarative settings path * test: keep the webhook doc comment with the test it describes * fix: warn on impossible schemes and ports, and validate the instance setting * feat: treat an empty allowlist as unset at both levels * perf: decode the cors path only when the fallback needs it * docs: document the empty allowlist as unset in the api schema * docs: describe an empty allowlist as unset in the frontend comments * docs: say what a null allowlist resolves to, not what it meant before the default existed * docs: state what the validator refuses and why methods stay broad * feat: exempt static asset routes from the origin allowlist * fix: hide the origin control for every static target, not just websites * fix: exempt only static websites, not single-file static assets * fix: warn on an unclosed ipv6 host in the origins advisory * fix: require assets present, not just the static website flag --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
7b89e37322 |
chore(main): release 1.811.1 (#11107)
* chore(main): release 1.811.1 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> |
||
|
|
62d4632fad |
chore(main): release 1.811.0 (#11098)
* chore(main): release 1.811.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> |
||
|
|
c90d1d95c2 |
refactor: make the app policy's principal the authority for its identity (#10440)
* refactor: make the app policy's principal the authority for its identity * fix: align the app backfill with the sibling migration and audit the uncached address * chore: refresh the sqlx cache after rebasing onto the merged base * fix: resolve the app execution address uncached, it decides the job's authorization * chore: cache the EE queries at the ref this branch pins * chore: cache the EE queries at the ref this branch pins * fix: derive the app draft's on-behalf-of address on read * chore: cache the query the draft derivation test added * fix: derive the app identity on the draft-table and version reads too * docs: state the draft resolver's authorization contract * fix: resolve a draft's principal against workspace membership only * chore: cache the membership lookup the draft resolver added * fix: drop an unresolvable draft's address instead of leaving it stale * perf: evict the address cache on change so app dispatch can read it * fix: evict on superadmin role changes, not only address changes * refactor: make the app policy's address optional instead of derived on read * fix: follow an external superadmin's rename into the apps that name them * docs: state the removal gate once, and correctly * refactor: drop the app-policy version constant that gated nothing * docs: drop the last reference to the removed constant * perf: read the address cache everywhere now that eviction reaches every replica * fix: keep persisted addresses off the cache the poller evicts asynchronously * docs: state where the cached address is accepted and where it is not * docs: keep the cache rule in one place and drop the stale premise * docs: sort the two lookups by how long a wrong answer lives * fix: resolve the schedule address uncached where it is written to the row * docs: name the release this actually ships in * perf: evict a superadmin's key per workspace instead of the whole cache * fix: evict every alias a superadmin principal can be spelled as * docs: describe the trigger as it is * docs: cover the round-tripped read in the cache rule * docs: record why a stale dispatch address cannot escalate * fix: validate a dispatch address against the principal's live binding * fix: carry the validated address through to the job row and token * fix: record the validated address on the job row, not the one handed in * test: run the substep tag check as the non-superadmin it means to test Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JY4bBCR1q2c5XB8s2r7Ysc * fix: rewrite a stored app address that disagrees with its principal Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JY4bBCR1q2c5XB8s2r7Ysc * docs: record the accepted staleness window of the cached dispatch address Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JY4bBCR1q2c5XB8s2r7Ysc * fix: record the validated address on the job's audit row Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JY4bBCR1q2c5XB8s2r7Ysc * docs: record the accepted rename race of pre-transaction identity resolution Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JY4bBCR1q2c5XB8s2r7Ysc * docs: separate the app's stored address from the derived one in the resolver doc Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JY4bBCR1q2c5XB8s2r7Ysc * docs: describe the job identity fast path the push comments skipped Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JY4bBCR1q2c5XB8s2r7Ysc * fix: backfill a legacy group-prefixed username as the group it names Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JY4bBCR1q2c5XB8s2r7Ysc * fix: resolve a schedule edit's identity before opening its transaction Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JY4bBCR1q2c5XB8s2r7Ysc * fix: never resolve a disabled member to a same-named superadmin Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JY4bBCR1q2c5XB8s2r7Ysc * docs: state what the email-change notify buys, and rewrap two comment lines Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JY4bBCR1q2c5XB8s2r7Ysc * fix: keep a group's runnables when offboarding a legacy group-prefixed member Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JY4bBCR1q2c5XB8s2r7Ysc * fix: read the app author from the stored address, as execution does Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JY4bBCR1q2c5XB8s2r7Ysc * docs: record the rename race's full consequence as a known, accepted limitation Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JY4bBCR1q2c5XB8s2r7Ysc * docs: record the keep-target group address case as a known, accepted limitation Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JY4bBCR1q2c5XB8s2r7Ysc --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
4a293cf77a |
chore(main): release 1.810.0 (#11079)
* chore(main): release 1.810.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> |
||
|
|
75d7bee178 |
feat: remove the viewer login status badge from public apps (#11090)
* feat: remove the viewer login status badge from public apps Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VXbCbgjgBgdg7t68VGWQnZ * fix: only fetch the global user when the no-access page shows it Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VXbCbgjgBgdg7t68VGWQnZ --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
6056ec7148 |
feat: let apps hide the viewer login status on public urls (#11089)
* feat: let apps hide the viewer login status on public urls Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FTWrfHeqcFMH8qWdsP6kEr * fix: apply the login status setting on deploy and regenerate mcp tools Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FTWrfHeqcFMH8qWdsP6kEr * fix: save the login status toggle immediately like its sibling toggles Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FTWrfHeqcFMH8qWdsP6kEr --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
7915540f66 |
chore(main): release 1.809.0 (#11045)
* chore(main): release 1.809.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> |
||
|
|
63cb46d7bb |
feat: add a minimal skin for the approval page and slack/teams (#11061)
* feat: add an approval skin to the approval page and slack/teams messages Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KK2Ye4PizykReZVuMEm3m9 * chore: point ee-repo-ref at the teams approval skin commit Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KK2Ye4PizykReZVuMEm3m9 * fix: resolve the approval skin from the step awaiting approval Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KK2Ye4PizykReZVuMEm3m9 * fix: shorten the slack approval message to fit the button value limit Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KK2Ye4PizykReZVuMEm3m9 * fix: rename skins to detailed/minimal and keep long slack messages Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KK2Ye4PizykReZVuMEm3m9 * feat: title the minimal approval page from the step and flow summaries Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KK2Ye4PizykReZVuMEm3m9 * feat: let wait_for_approval set the description approvers see Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KK2Ye4PizykReZVuMEm3m9 * fix: keep a finished workflow's approval description, still gated Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KK2Ye4PizykReZVuMEm3m9 * fix: keep a login-required approval locked after the run moves on Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KK2Ye4PizykReZVuMEm3m9 * feat: hide the windmill version on the approval page Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KK2Ye4PizykReZVuMEm3m9 * chore: update ee-repo-ref to e92abc9d1fba3ba898640df0cfeb0af8a50849b4 This commit updates the EE repository reference after PR #786 was merged in windmill-ee-private. Previous ee-repo-ref: bf1766ff49458f62d3f11746f1a06893ae3c2325 New ee-repo-ref: e92abc9d1fba3ba898640df0cfeb0af8a50849b4 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> |
||
|
|
8bd144ccb1 |
keep crawlers off the login page (#11057)
* fix(frontend): keep crawlers off the login page Every page on the hub links to /user/login with itself in `rd`, so a crawler sees one login URL per hub page — 4,311 of them in Search Console, all rendering this same form and flagged as duplicates without a canonical. Nothing about a login page belongs in an index, on any instance. Mark the page noindex, as public_run already is, and ship a robots.txt that keeps crawlers out of /user/ and /api/. The frontend is embedded as static assets with an index.html fallback, which is why /robots.txt answered with the app shell until now; a real file in static/ is served as itself. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(frontend): let crawlers fetch the login page so the noindex is seen robots.txt disallowed /user/, which stopped a crawler fetching /user/login at all — and a page that is never fetched never shows its noindex. The two halves cancelled: the URLs would have moved from "duplicate" to "blocked" rather than out of the index. Drop the disallow, keeping /api/. And since the app is client-rendered, the meta tag only exists after a render pass; send X-Robots-Tag on /user/* from serve_path as well, which a crawler sees on the first fetch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
569adb85c1 |
feat: live queue status per tag and bounded queue metric charts (#11067)
* feat: live per-tag queue status and bounded charts in the queues drawer Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011fYMqh7R8FnePmbYzirJXZ * fix: drop stale chart failures and test the queue metrics series query Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011fYMqh7R8FnePmbYzirJXZ * fix: name the queue status refresh, skip overlapping polls, soften the no-worker warning Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011fYMqh7R8FnePmbYzirJXZ * feat: draw a stuck tag's queue delay exactly as it climbs (#11071) * feat: store a stuck tag's queue delay as its head's wait start Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011fYMqh7R8FnePmbYzirJXZ * fix: keep a climb's top inside a slot and stamp held delays exactly Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011fYMqh7R8FnePmbYzirJXZ * fix: redraw a climb as soon as its head leaves, and document the lookup slack Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011fYMqh7R8FnePmbYzirJXZ --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
f517402538 |
fix: bound list_jobs runtime and paginate runs on the sorted column (#11072)
* fix: bound list_jobs runtime and paginate runs on the sorted column Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CRqFF6xk1A41AcF6NsTDNS * fix: keep queue-only refresh unbounded and page runs by exact inclusive cursor Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CRqFF6xk1A41AcF6NsTDNS * fix: cap tie-widened pages at the server limit and make the list timeout configurable Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CRqFF6xk1A41AcF6NsTDNS --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
ab9efc897c |
fix: refuse cross-site GET requests that run Hub scripts (#11054)
* fix: reject cross-site GET requests on job-run endpoints Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015V51NZ7yeRzbJCsq5n4tzd * fix: log the Referer leg of the cross-site guard and unit-test host parsing Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015V51NZ7yeRzbJCsq5n4tzd * fix: scope the cross-site GET guard to Hub scripts Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015V51NZ7yeRzbJCsq5n4tzd * refactor: resolve script runnables through the cross-site guard Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015V51NZ7yeRzbJCsq5n4tzd --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
9a563f6d72 |
perf: index the FK columns that cascade on workspace delete (#11052)
Claude-Session: https://claude.ai/code/session_01UtSL61CGQqtLc28AT2h75d Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
133e21080a |
chore(main): release 1.808.0 (#11043)
* chore(main): release 1.808.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> |
||
|
|
22c1a106cf |
chore(main): release 1.807.0 (#11034)
* chore(main): release 1.807.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> |
||
|
|
0b63e0a692 |
feat: make guest access unavailable on the shared cloud (#11040)
* feat: make guest access unavailable on the shared cloud Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NiPw5gUgNJPxtGG1meS6RY * test: pin that an issued guest session stops on the shared cloud Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NiPw5gUgNJPxtGG1meS6RY * fix: refuse only widening an app into guests where they are unavailable Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NiPw5gUgNJPxtGG1meS6RY --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
fd35b47658 |
feat: create the cloud workspace in onboarding, and teach the empty home (#10959)
* [ee] feat: create a personal workspace on cloud signup instead of the demo invite Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012fRjnaHLwjpHN84gNNxah9 * feat(frontend): land cloud users in their workspace after onboarding Cloud signup creates exactly one workspace for the new user, so the picker that followed onboarding was a page with a single choice on it. Switch to that workspace and go to the home page instead, falling back to the picker whenever there is a real choice: an invite to accept, several workspaces, or none. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012fRjnaHLwjpHN84gNNxah9 * chore(frontend): remove the tutorial system Deletes the guided-tour feature: the tutorials directory, the per-editor wrappers, the home banner and button, the /tutorials route, and the driver.js dependency they were built on. Also removes what only existed to serve them — the `tutorialsToDo` / `skippedAll` / `isCurrentlyInTutorial` stores, the `disableTutorials` prop chain through the flow editor, the `?tutorial=` deep links, PopupV2's clickOutside exemption for the driver popover, and the selector-anchor class on the flow editor tabs. The backend `tutorial_progress` endpoints and table stay: nothing calls them now, and removing them is a public-API break plus a migration that would drop existing progress. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012fRjnaHLwjpHN84gNNxah9 * feat(frontend): suggest Hub projects on an empty workspace home A workspace with nothing in it showed only "Welcome to Windmill". Replace that with a grid of ready-made Hub projects to import, and hide the search box, kind toggles and the sort/filter row while the workspace is empty — they would act on an empty list. A search that matches nothing still keeps its controls and shows the no-match message. The project list is seeded locally for now; the Hub endpoint that ranks them is not there yet, and Import is still a placeholder. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012fRjnaHLwjpHN84gNNxah9 * feat(frontend): make a new cloud workspace the thing onboarding produces Signup already makes a personal workspace; nothing let its owner name it, and a user who ended up without one landed on a workspace picker whose only action was a button. Onboarding now ends on the workspace itself, and the empty home that follows says what a workspace is for rather than "Welcome to Windmill". Onboarding gains a third step that names the workspace signup created, prefilled from the login provider's name or the email local part — `ruben@…` gives "Ruben's workspace". Skipping the survey reaches it too: the questions are ours, the workspace is theirs. Advanced settings swaps in the real creation form for someone setting up for a team. The workspace picker stands down when it has nothing to offer: no workspace to enter and no invite to accept leaves one action on the page, so the page is that action — one field, prefilled, "Create workspace". Both hand-overs hold a loading state for 900ms and the app fades in behind them, so creating a workspace reads as something that happened. The empty home draws three static placeholder rows in the shape of real ones, under a caption offering a template or the New menu. "Start from a template" opens a popover listing the hub's projects, most-starred first, preloaded when the empty state renders and paged as you scroll. Picking one opens the import wizard in a dialog: its two destination steps are already answered by being in a workspace, so it starts at the import itself and pages to the credentials step with the animation the paged-modal pattern provides. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012fRjnaHLwjpHN84gNNxah9 * fix(frontend): gate the empty state and repair the derived workspace id Review findings from the first round. The empty state offers a template import and the New menu, and neither checks a permission — so an operator, or a workspace whose direct-deploy protection cleared `showEditButtons`, was offered both. It now sits behind the same gate as the create menu thirty lines above it. `validateWorkspaceId` answers with the *reason* an id is unusable, so `if (validateWorkspaceId(next)) break` stopped on the first invalid candidate and returned it: someone named Global got the reserved `global`, and a 50-char seed got a taken one. Invalid candidates are skipped instead, and when none works the caller opens advanced settings rather than posting a name the server refuses. Also: `rd` may be absolute (the CLI login sends one) and `goto` refuses those, which would strand the caller on the "Creating …" screen with the workspace already made; the hub host is parsed defensively, since the instance setting is whatever an admin typed and `new URL` was throwing in render; `insert_workspace` says which authorization its callers still own; and the two arrival animations' comments now describe when they actually play. Tests for the two pure helpers the review named: `defaultWorkspaceName` and `hubProjectDescription`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012fRjnaHLwjpHN84gNNxah9 * fix(frontend): use unifiedSize on the new onboarding step's buttons `size` and `color` are deprecated on Button; the new step copied them from the survey steps above it. AGENTS.md: deprecated props survive at old call sites, copying one forward is still a bug. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012fRjnaHLwjpHN84gNNxah9 * fix(frontend): let Skip wait for the workspace onboarding names `loadWorkspaceStep()` was fired and dropped, while Skip and the use-case Continue branch on `ownWorkspace` in their `finally`. Skip awaits one POST that starts after those two GETs and can finish before them, so a first-frame Skip fell through to `leaveOnboarding()` and landed in the workspace with the backend's name — the step this flow exists for, silently gone. Both exits await the load; `isSubmitting` already covers the wait. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012fRjnaHLwjpHN84gNNxah9 * refactor: create the workspace in onboarding rather than at signup Signup no longer makes a personal workspace, so the last onboarding step creates one instead of renaming it — the same one-field `SimpleCreateWorkspace` the workspace picker falls back to, so a user who leaves onboarding early meets the form again rather than something new. The id now comes from the name they type rather than from their email, and there is one creation path instead of two. `insert_workspace` goes back to being private: the extraction existed only so the EE signup path could call it, and nothing outside `create_workspace` does now. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012fRjnaHLwjpHN84gNNxah9 * fix(frontend): count a pending invite as somewhere to go Onboarding read `listUserWorkspaces`, which returns membership. An invite is a `workspace_invite` row until `accept_invite` runs, so an invited teammate reached the last step owning nothing and was walked into creating a personal workspace, with the invite nowhere on the page. Invites are fetched alongside the workspaces, the way the picker already gates the same decision. A failed load now reads as placed rather than not: the picker can work the decision out, while the create step's only way forward is creating. The create form reports when it is handing over, so the Previous button beside it stands down for the ~900ms rather than offering a way back out of a workspace that now exists. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012fRjnaHLwjpHN84gNNxah9 * fix(frontend): open the template picker downward and size it from the popover The popover's default positioning caps its height to the viewport, and the list inside it carried a fixed one, so a capped box overflowed its own frame — visible with the AI composer hidden, where the caption sits high and `placement: top` left almost no room above it. It opens downward now, with flip fallbacks, at a definite `min(72vh, 520px)`; the list fills what the header leaves, which is still the definite height it needs to page. `creating` on the create form becomes `onCreatingChange`: `$bindable(default)` on an optional prop is banned, and this is something the form reports rather than state it shares. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012fRjnaHLwjpHN84gNNxah9 * refactor(frontend): drop the InfiniteList containerClass prop Added for the template picker, which turns out not to need it: DataTable's own container is already `h-full`, so `containerClass="h-full"` merged to nothing and the height the list pages against comes from the flex chain above it. A prop with no effect at its only call site is public surface for free. InfiniteList is back to what it was. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012fRjnaHLwjpHN84gNNxah9 * fix(frontend): stop a dismissed project import from running on Modal reports dismissal only through its bindable `open` — the X, Escape and the backdrop dispatch neither `confirmed` nor `canceled`. Bind it, so clearing `pick` follows the dialog closing: re-picking the same project opens it again, and a run still in flight is abandoned with a toast instead of writing to the workspace with no UI in front of it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012fRjnaHLwjpHN84gNNxah9 * fix(frontend): stop a dismissed project import from running on Modal reports dismissal only through its bindable `open` — the X, Escape and the backdrop dispatch neither `confirmed` nor `canceled`. Bind it, so clearing `pick` follows the dialog closing: re-picking the same project opens it again, and a run still in flight is abandoned with a toast instead of writing to the workspace with no UI in front of it. Also mark the inline-link buttons as sanctioned rather than oversights, and give Log out `text-accent` instead of `text-blue-500`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012fRjnaHLwjpHN84gNNxah9 * fix(frontend): keep the stopped-import toast off the Finish path `done` survives a retry, so Finish is clickable while the run is going again, and its own closing reaches the same falling edge the X does. Abandon the run either way; say it was stopped only when that is what the click asked for. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012fRjnaHLwjpHN84gNNxah9 * feat(frontend): reach the hub importer from the create menu, and count the funnel The picker only existed inside the empty state, which disappears as soon as a workspace holds one item — nothing else in the product linked to `/projects/import`. New → Import now offers a hub project, opening the catalogue in a dialog: a popover anchored to an item inside an open dropdown leaves two melt layers arguing over focus. The list and the import dialog move up to ItemsList, so one dialog serves both doors. `template_setup` records how the credentials step ended — `filled` only when nothing was outstanding, `skipped` carrying how many rows were left — and `template_abandon` records where a dismissed import was given up. `template_picker_open` gains a key naming the entry point. Also on the workspace picker: logging out is a text link on the line that says who you are and an item in the settings menu, rather than the page's accent action, and onboarding's Previous joins the row it belongs to instead of hanging under the button that finishes the form. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012fRjnaHLwjpHN84gNNxah9 * fix(frontend): make the import counters answer what they claim to `template_abandon` folded a landed import closed with the X into `idle`, the bucket read as "opened this and bounced" — three outcomes in one number. It gets its own `done` stage. `template_setup` counted skipped rows through `value`, which is an increment: `skipped` accumulated rows while `filled` and `none` counted imports, two units in one counter with no way to recover one from the other. The row count becomes a bucket in the key, so every event is one import and the buckets compare. The import dialog also asked for the hub URL settings at init, and the home list now mounts it for everyone on every arrival — two GETs for a string only the project card renders. Deferred to the first pick. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012fRjnaHLwjpHN84gNNxah9 * fix: sanitize the inferred username, and stop counting an unread setup step as clean `loadUsernamePolicy` derived a username by stripping dots, so `O'Connor` and `alice+demo` both produced values the `proper_username` constraint refuses — posted invisibly by the simple form, which then failed with nothing on screen explaining why. `usernameFromName` keeps only `[\w-]` and answers undefined when nothing usable is left, which is already the form's cue to open the full one. The credentials step offers Finish when the export could not be read, since it cannot tell what is outstanding — and that landed in `template_setup` as `filled`, the bucket meaning the step came out clean. It reports whether it checked anything, and an unread step counts as `unchecked`. Also drops an orphaned `.sqlx` entry left by the create-at-signup query this branch abandoned, and rewrites the stepper's first-frame comment, which argued from a meaning of `resourceCount` that main has narrowed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012fRjnaHLwjpHN84gNNxah9 * fix(frontend): cap the inferred username at what the column holds `usr.username` is VARCHAR(50) while the provider name and email it is derived from run to 255, and `create_workspace` inserts the value untruncated — so a long first name failed the same way the invalid characters did: posted invisibly, refused on insert, with nothing on screen naming the field. Undefined instead, which the form already routes to the full one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012fRjnaHLwjpHN84gNNxah9 * fix(frontend): show the import note where it applies, and drop onCreated's unused id The note is about landing on top of what a workspace already holds, so it belongs wherever the destination is an existing workspace. The route already read it that way; the dialog, which always imports into the current workspace, was hiding it. It costs one collapsed row. `onCreated` was typed as taking the new workspace id, and the advanced branch passed `''` because `CreateWorkspaceInner` does not report one. No caller reads it — the form has already switched to the workspace by then — so the argument goes rather than the lie staying. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012fRjnaHLwjpHN84gNNxah9 * fix(frontend): keep the empty-state toolbar reachable, and name the view counters "Empty" here means the default listing found nothing, and a workspace whose items are all archived looks exactly the same. The searchbar carries "Only archived", so taking it off the pointer left those items unreachable without hand-writing a query URL. Dimmed still, never `inert`. The disclosure named the counters that fire on a creation or an import and not the three that fire on merely seeing the empty home or opening either picker. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012fRjnaHLwjpHN84gNNxah9 * fix(frontend): respect disable_hub in both hub-project entry points An instance with the hub turned off still got the catalogue preloaded on every empty home and an "Import a hub project" entry in the create menu — an outbound request the operator has said not to make, and a door to somewhere unreachable. Both now observe `disableHubStore`, the store the script and flow hub pickers already read. With the hub off the caption reads "Create a new one." rather than continuing a sentence whose first half is gone. The telemetry disclosure also scoped the create menu and picker counters to the empty home, when both fire from the toolbar in a populated one, and said a creation was recorded when what is recorded is the menu opening. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012fRjnaHLwjpHN84gNNxah9 * refactor(frontend): drop the catalogue preload rather than gate it twice Warming the hub catalogue when the empty state rendered bought the time between the caption appearing and someone clicking it, and cost two defects: the request fired on instances with the hub turned off, and the gate added for that raced `disable_hub`'s own load, which starts false and stays false if the settings request fails. The picker fetches on open instead. Measured: nothing before the click, one request after it, 411ms to a filled list. `disableHubStore` still hides the link and the menu entry, which cost no request and correct themselves if the setting lands late. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012fRjnaHLwjpHN84gNNxah9 * docs: count the home actions, and state the lazy-fetch constraint without its history The telemetry doc's tally is maintained by hand and main had just moved it; this PR adds a feature, so it reads 48 across eighteen with `home` in the list — verified against the pinned EE ref rather than counted by eye. The empty state's comment narrated a preload that no longer exists and the defects it caused. What a future reader needs is the constraint: `disable_hub` loads asynchronously, so a fetch from here goes out before the setting forbidding it is known. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012fRjnaHLwjpHN84gNNxah9 * fix(frontend): stop the prefill overwriting a typed name, and gate submit on the policy `load()` assigned the suggested name unconditionally, so a name typed while its two requests were in flight was replaced a moment later. It now yields to anything already typed. Nothing may be submitted before the username policy lands either: `automateUsername` starts at the common case, and posting that guess to an instance that derives no usernames sends none where one is required. `policyLoaded` gates both the button and `create()`, and is set in a `finally` so a failed load leaves the form usable rather than wedged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012fRjnaHLwjpHN84gNNxah9 * fix(frontend): settle the username policy on failure instead of guessing it `policyLoaded` was set in a `finally`, so a failed policy load unblocked the form with `automateUsername` still at its default — the exact submit the flag exists to prevent. The failure now hands over to the full form, which asks for a username outright rather than inferring one, so the flag is never true while the answer is still a guess. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012fRjnaHLwjpHN84gNNxah9 * fix(frontend): answer the username policy instead of rejecting it Three rounds of this bug moved between call sites because the shared loader rejects when it cannot read `automate_username_creation`, leaving each caller to guess — and both guessed "automated", which hides the username field and posts none to an instance that derives none. `loadUsernamePolicy` now answers "ask for one" in that case, so `SimpleCreateWorkspace` and `CreateWorkspaceInner` both render a field someone can type into rather than submitting a guess. An instance that does automate ignores a username it was sent, so asking is safe either way. The prefill and the policy are settled apart now too: a failed `globalWhoami` costs the suggested name and nothing else. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012fRjnaHLwjpHN84gNNxah9 * fix(frontend): block creation when the username policy is unknown There is no safe default. `create_workspace` refuses a username on an instance that automates them and requires one on an instance that does not (`workspaces.rs:5820`), so a client that cannot read the setting has two request shapes available and the server rejects both. Last round's "ask for one" was as wrong as the "automated" guess it replaced. So the loader reports the failure instead of inventing an answer, and the form says so: Create stays disabled, with a line explaining why and a link to try again. Verified in the browser both ways — unreadable policy disables Create and shows the message, a healthy load prefills the name and enables it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012fRjnaHLwjpHN84gNNxah9 * fix(frontend): close the advanced-settings bypass while the policy is unknown Create was gated on knowing whether the instance derives usernames, and the link beside it went to a form with no such gate — so the way around the block sat next to it. It is disabled until the policy is known, with a title saying why. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012fRjnaHLwjpHN84gNNxah9 * fix(frontend): reload the list on dismissal, and never re-offer a workspace that exists Closing a landed import with the X left the home list stale: only `finish()` reloaded it, so a workspace that now holds a project kept showing its placeholder rows. A run that started wrote items whether it finished, was abandoned or failed partway, so any dismissal after one reloads. Creation reported failure for a failed *list refresh* too, and handed the form back — where a retry picks the next free id and creates a second workspace. Once `createWorkspace` returns, nothing may report failure: the refresh is logged if it fails, and the hand-over proceeds, since the workspace is real either way. The disabled-link tooltip also claimed the settings could not be read during the ordinary load, before anything had failed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012fRjnaHLwjpHN84gNNxah9 * fix(frontend): reload after the abandoned run stops, not when it is asked to `abandon()` stops the run at the next phase boundary; the request already sent still lands. Reloading the list at that moment could read it before that write committed, leaving the caller stale again — the thing the reload was added to fix. It now waits for `running` to clear, which is immediate for the common case of dismissing a finished import, with a cap so a run that never settles still ends in a reload. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012fRjnaHLwjpHN84gNNxah9 * fix(frontend): give the list reload one owner, taken by both exits Finish reloaded immediately while dismissal waited for the run to stop, so Finish pressed during a retry — `done` survives one, which is what makes the button clickable then — read the list mid-write, and its `finishing` flag stopped the deferred reload from correcting it. Both exits now go through the same wait. One reload per closing, always after the writing stops, whichever way the dialog was left. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012fRjnaHLwjpHN84gNNxah9 * feat(frontend): give ImportExecution a whenIdle(), and await it instead of polling The reload waited on a 250ms poll of `running` with a 15s cap, because the modal receives the execution after `run()` was already called and so holds no promise to await. The cap was its own hole: a write slower than 15s reloaded early, and nothing followed. `run()` now keeps the in-flight promise and `whenIdle()` hands it out — resolved when nothing is being written, immediate when no run is in flight. The modal awaits that: no poll, no cap, no window. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012fRjnaHLwjpHN84gNNxah9 * fix(frontend): back the settlement reload with a bound, and keep reporting run failures `installProject` writes serially and takes no signal, so a request left pending after earlier items committed leaves those invisible until the next page load — `whenIdle()` alone never resolves for it. A bound now reloads once in that case, *without* replacing the settlement reload: replacing it was the flaw in the timeout this grew out of, so a hung run reloads on the bound and again if it ever finishes. `whenIdle()`'s rejection handler also swallowed the only report an unexpected throw had — `#runInternal` has no catch of its own, and a throw outside its inner ones leaves a stalled run with nothing on screen. It logs now instead of discarding. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012fRjnaHLwjpHN84gNNxah9 * feat(frontend): say when a workspace holds only archived items A workspace whose items are all archived read as empty, because the placeholder is decided by the default listing. Reaching those items then depended on the toolbar, which is why it had been left interactive while dimmed — and that let a kind toggle replace the invitation with "no items found" on a workspace that really was empty. The state is named instead. When the default listing comes back empty, one request asks whether anything archived exists, and the placeholder says which of the two it is: "Everything in this workspace is archived" with a link to show them, or the ordinary invitation. Held until that answer lands rather than drawn and swapped, since the wrong one claims the workspace is empty when it is not. The toolbar is dimmed and `inert` again, its original design: the archived case now carries its own way in. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012fRjnaHLwjpHN84gNNxah9 * fix(frontend): make the archived route independent of write permissions Three ways the archived-only placeholder failed to deliver what it promised: Reading archived items is not a write, but the notice offering them sat behind the create-permission gate — so an operator, or a workspace whose direct-deploy protection cleared `showEditButtons`, got "no items found" over items it could see and a toolbar now inert. The gate governs the create actions alone; the notice is shown to whoever the probe found something for. The probe answered once per workspace and was never invalidated, so archiving the last item left a cached "nothing archived" claiming the workspace was empty until a page load. `reloadItemsAndCounts` clears it. And it omitted `includeWithoutMain`, which the backend reads as excluding library scripts — a workspace holding only archived ones answered "empty". Always true here: hiding library scripts puts a filter in `activeFilters`, which `workspaceEmpty` requires to be empty. `whenIdle()` gains the two tests its contract deserves, since the reload correctness three rounds argued over rests on it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012fRjnaHLwjpHN84gNNxah9 * fix: harden the hub proxy, the workspace picker's gating and the rd hand-off Findings from four local review passes over the branch: - `list_projects` refuses when `disable_hub` is set, and `is_public_hub` now compares the parsed host, so no spelling of the public hub (mixed-case scheme or host, port, trailing dot, userinfo) forwards a member's bearer token there. Covered by a unit test table. - The workspace picker waits on `usersWorkspaceStore` as well as `workspaces`, which derives to `[]` while the store is unloaded; with the create-form latch, one such frame swapped a member's picker for the create form until reload. - `refreshSuperadmin` takes `force`, and the picker uses it: a `false` left over from a logged-out load decides whether the page is a picker or a create form. A cancelled call no longer publishes `false` over the live request's answer, and only its own request's handle is cleared. - `rd` is sanitized once where it is derived rather than at each of the four hand-offs, so an absolute target keeps the OAuth callback's allowance and `https://evil.example/` is dropped. - The archived-items probe answers "unknown" on failure, which keeps the ordinary caption and leaves the toolbar reachable. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012fRjnaHLwjpHN84gNNxah9 * fix: answer the round-33 nits on the hub route and the empty state - `empty_state_view` is no longer logged for an archived-only workspace, which is not the state the counter measures. - `detail`'s fetcher keeps its last answer in a local instead of reading `detail.current`, a self-reference that typed the resource `any`. - `list_projects`' comment, including its authorization contract, is back on the handler rather than on the predicate inserted above it. - `listHubProjects` documents the 400 an instance with the hub disabled returns. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012fRjnaHLwjpHN84gNNxah9 * feat(frontend): keep the operator onboarding tour Operators cannot create anything, so the home page is the whole product to them and its three tabs are worth naming. The tour that did that is the one piece of the removed tutorial system that still has an audience. Restored trimmed: driver.js, the driver wrapper and its controls, the `.driver-popover` styling, and a module for the progress bit. The catalogue machinery it used to sit in — the config, the role gating, the router, the banner and the tutorials page — stays deleted, so the five steps are reached directly instead of through a registry of one. It runs on an operator's first home page visit and is recorded as seen however it ends, including navigating away; afterwards it is in the sidebar menu under Take the tour, which is where the last step points. Progress uses the surviving `tutorial_progress` route, slot 6, read-modify-written so the slots of the removed tutorials keep their state. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JirHCYVR6qg7Xqe4PcZ1KG * fix(frontend): dim the home toolbar only where the placeholder replaces it Standing the toolbar down depends on something else offering a way onwards. An operator in a workspace that is simply empty gets no placeholder — they cannot create, and there is nothing archived to reach — so the search and the kind toggles were the only controls on the page, dimmed to 40% and `inert`. They now follow the placeholder rather than emptiness, which also stops the operator tour spending three of its five steps highlighting controls this page had greyed out. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JirHCYVR6qg7Xqe4PcZ1KG * docs(frontend): put the inline-link rationale on the first link, not the third The caption's three links share one reason for being bare `<button>`s, and it was written on the last of them. A reader — or a reviewer — meets the archived one first. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JirHCYVR6qg7Xqe4PcZ1KG * fix(frontend): keep private hub project names out of telemetry An instance pointed at its own hub imports its own projects, and the slug naming one is the customer's content — `template_import` was recording it verbatim, which the disclosure ("the name of any public hub project") does not cover and `hub_script` already avoids by collapsing a private script to `private`. `hubProjectUsageKey` gives projects the same treatment, deciding by the configured hub's host so a port, a scheme's case or a trailing slash cannot turn a private hub into a public one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JirHCYVR6qg7Xqe4PcZ1KG * fix(frontend): treat an unread hub setting as private, not as the public hub `hubBaseUrlStore` is seeded with the public hub and written in one place, by a loader with no catch and no retry. A settings read that threw therefore left the store naming hub.windmill.dev for the rest of the session, and the import counter read that as permission to report a private instance's project slug — the leak the previous commit closed, narrowed to "after one failed read". The fact has three states and the store held two, so `hubBaseUrlKnown` carries the third: the loader sets it only once the value is the instance's own, and the telemetry key requires it. Links keep rendering the default meanwhile, which is what they always did. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JirHCYVR6qg7Xqe4PcZ1KG * fix(frontend): tag each hub detail with the slug that asked for it `resource()` assigns whatever its fetcher returns, with no guard for a run that has been superseded, so handing back the previously fetched project on a stale response published that project. With two slow requests in flight — pick A, leave B loading, pick C — B's answer put A's name, author and counts on the card while the plan underneath still said C, and Import wrote C. Each answer now carries its own slug and is read only while that slug is the chosen one, which also drops the local the previous shape needed to keep the resource's type from going circular. The hub-telemetry tests reset their shared fixture per case; the private-hub one had been passing on what the case above it left behind. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JirHCYVR6qg7Xqe4PcZ1KG * fix(frontend): stop the template picker spinning on a hub with no projects Opening the picker against a reachable hub that has published nothing pinned the renderer at full CPU and froze the tab. The effect arming the list called `setLoader` and `loadData`, which read `InfiniteList`'s reactive state as well as writing it, so the effect depended on what its own load changed and re-ran itself; a list that stays empty never settles that cycle. It now arms the loader once per workspace, untracked, and leaves the load to `setLoader`. The same empty list also claimed the hub was unreachable, since one `empty` snippet serves both. The loader records which happened, so a hub with nothing on it says so. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JirHCYVR6qg7Xqe4PcZ1KG * fix(frontend): keep a superseded hub answer from erasing the chosen project `resource()` publishes whatever its fetcher returns, superseded or not, and `fetchHubProject` takes no abort signal — so an answer for a project the user had moved on from replaced the published value, the slug guard rejected it, and the chosen project's item counts went off the card for good with nothing left to ask for them again. The fetch now records its own answer, tagged with its slug and only while that slug is still the chosen one, and the card reads that. Nothing reads the resource, so it is a `watch` — the same machinery without the value that was the problem. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JirHCYVR6qg7Xqe4PcZ1KG * chore: update ee-repo-ref to 81edd1382d951265ab3e9b67fc7ca7967676fd56 This commit updates the EE repository reference after PR #775 was merged in windmill-ee-private. Previous ee-repo-ref: 21ace847ec1c1406bafc50153004e1874642bf6c New ee-repo-ref: 81edd1382d951265ab3e9b67fc7ca7967676fd56 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> |
||
|
|
63c40f046b |
chore(main): release 1.806.0 (#11012)
* chore(main): release 1.806.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> |
||
|
|
448fce93f7 |
fix: make the native trigger disable/enable toggle actually save (#11024)
* feat: let a native trigger be disabled without deleting it Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: show and control the native trigger pause outside the flow editor Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: create a native trigger already paused instead of pausing it after Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: correct the native trigger enabled comments for create-time init Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
9444049d60 |
feat: bring gitlab repositories to parity for git sync (#10938)
* feat: track and rotate gitlab git-sync repository tokens * chore: point ee-repo-ref at the gitlab credential branch * fix: strip server-owned credential status and correct expiry copy * fix: gate credential maintenance on enterprise and alert on stalled renewal * fix: alert on an auto-renewed token only once it has actually expired * feat: receive gitlab push webhooks for instant git sync pull * feat: open gitlab merge requests and post diff previews on them Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: keep gitlab merge request previews out of the project's own pipeline Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: bound the credential maintenance pass and gate the gitlab picker on a license Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: create the gitlab picker's variable in the edited workspace Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: make the gitlab picker's variable path collision-resistant Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * docs: state the gitlab scope and rotation facts the code relies on Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: resolve the check marker's repository from its path, not a stored url Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: refuse to finish a check whose repository has been repointed Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: trust a check marker's captured url when it carries no identity Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: recreate a missing webhook from credential maintenance Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * docs: state that relative-url gitlab installs are out of scope Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: keep credential status out of exports and clear stale webhook warnings Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: refuse an unprovable check and guard the picker on the stored repository Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: re-check the picker's target path at the moment it is written Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: snapshot the picker's inputs before it starts writing Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * docs: recommend a project access token per repository Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * [ee] feat: keep the git-sync credential in workspace settings * [ee] fix: drop a removed repo's credential and honor the workspace override * [ee] fix: resolve a fork's git credential from its whole ancestry * [ee] refactor: reuse fork_ancestor_chain instead of a second ancestry walk * [ee] fix: resolve an app installation from the whole ancestry, not the parent * [ee] revert: keep the app installation fallback at one level * fix: store the git credential only once the resource is saved * fix: keep a repository's credential when it leaves git sync settings * docs: cut the gitlab picker's token guidance down to what it needs * feat: mark a repository whose credential windmill holds * fix: ignore the managed-credential marker when the url carries a token * docs: drop the picker's setup alert for a line by the token field * feat: replace a repository's stored token from its resource * fix: store a picked credential for its own workspace, before the resource * refactor: key a stored git credential by its repository, not its resource Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * chore: refresh the sqlx cache for the repository-keyed credential queries Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: gate the credential pass budget on the features that use it Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: decide credential rotation ownership by repository, not resource path Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * refactor: renew only the credentials windmill holds, not tokens in a repo url Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: authenticate the fork-branch poll and correct the renewal guidance Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: do not claim a managed credential for a url the client cannot resolve Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: define the credential facade for private builds without enterprise Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: pin the listed token before the await and name the real renewal blocker Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: pin the token the replace flow checked, and derive the scope test once Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: classify the renewal state once so the card cannot contradict itself Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * refactor: ask only whether the token gets renewed, not why it does not Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * refactor: replace the managed-credential marker with a server answer Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: read renewal from the credential and its origin, not a removed field Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: read the provider for url-token repos, await the origin before defaulting, and visit unchecked repos last The maintenance pass sorted repositories with no recorded check first on the premise that they cost nothing, but a token-in-URL remote on a host that is not GitLab is probed every pass and never records a check, so it held the head of the list ahead of the tokens that expire. Such repositories now sort last. The card decided its delivery defaults before the origin lookup landed, so a freshly picked GitLab repository never got webhook delivery; the two lookups are awaited together. The resource editor offers to replace a token only where it is held, not in a fork that borrows it, and the replace flow refuses a URL it cannot parse instead of keying the token to it. Attaching a stored credential to a commit-hash probe now requires admin, matching the installation credential beside it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * docs: describe the gitlab listing token the way the picker and the setup guide do Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * refactor: a token in the repository url is a plain remote, not a tracked credential Drops the status fingerprint that told one URL token from another, the docs' promise that such a token's expiry is reported, and the test's expectation that a URL-token repository declares a host. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: the card reads the credential origin for managed controls and honours the licence for a borrowed token Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * chore: bump the ee ref Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: hide a repository's credential line once nothing is held for the repository it names Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * docs: describe the exported credential status as it is Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * refactor: run the credential maintenance pass as its own task, without a budget The pass ran inside the monitor's join, whose deadline cancels every future in it, and a rotation cancelled between GitLab issuing a token and Windmill storing it loses the token family. A wall-clock budget with a least-recently-checked ordering kept it under the deadline. Spawning the pass instead makes the deadline irrelevant, so the budget, the ordering and the counter go; the advisory lock keeps a slow pass from overlapping the next, as it already did. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * docs: say what detaching the maintenance pass buys, and what it does not Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * chore: run git sync on the hub script version that reads a stored credential Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * chore: run the deploy push and the connection test on the hub versions that read a stored credential Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: keep App repositories and plain remotes out of the stored-credential paths Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * chore: host-neutral deploy preview wording, drop the project filter from the GitLab picker Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * chore: bump ee ref, rotation no longer retains a second connection per repository Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * chore: bump ee ref, the rotation write-back holds a single connection Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * chore: hold the credential maintenance lock in a transaction so a dead sweep releases it Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * docs: describe the credential-stored callback as it fires Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * fix: keep the credential maintenance lock past the pool's idle-in-transaction timeout Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C1xHmkxuxYb1GYvth1BS75 * chore: update ee-repo-ref to e092518ee60e33160fee9ae91a4d109566f7b0ee This commit updates the EE repository reference after PR #771 was merged in windmill-ee-private. Previous ee-repo-ref: 74481f7cc345757aebb2a8b04d3a22978328c348 New ee-repo-ref: e092518ee60e33160fee9ae91a4d109566f7b0ee 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> |