mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-22 00:01:34 +00:00
ah/session-task-manager
405 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
c09de594b6 |
feat: version resource values with history, diff and restore (#10596)
* feat: version resource values with history, diff and restore Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: record resource versions in a trigger so direct writes are covered Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: show the selected version's value and tighten history write access Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * perf: gate resource version recording in trigger WHEN clauses Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: clear a resource's past versions, and address review nits Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: restore the displayed version and keep author attribution on pooled writes Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: scope history to the selected workspace and gate clearing on ownership Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: gate restore on write access and clearing on the signed-in workspace Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor(frontend): share the version-history row between script and resource drawers Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * perf: trim resource version history in the monitor sweep, not on write Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(frontend): match the script versions drawer shell for resource history Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * perf(frontend): highlight version values instead of mounting monaco Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(frontend): match the script drawer's code preview presentation Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: rank version trim in one windowed pass instead of a correlated delete Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor(frontend): treat the newest version as current by position Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * perf: gate the resource version trim to an hourly sweep Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: unnest the version row action and correct the trim cadence docs * perf: cap the history listing and use sets for reference lookup * feat: warn when a resource is written more than 60 times a minute * fix: lower the resource write advisory to 20 per minute * fix: discard stale history loads and never diff against an unread value * fix: correct the write advisory boundary and document the eviction lock * fix: read history and the live value from one snapshot * refactor: read the drawer's diff baseline from versions, not the live resource * fix: open the history drawer with no version selected * fix: disarm the clear confirmation and clear the pane when the selection moves * fix: explain the missing diff and drop a guard that can no longer fire --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
5ce29b3436 |
feat: add public sharing option for job pages (#10573)
* feat: add public sharing option for job pages Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: gate public run sharing and address review findings Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: address review nits on public run sharing Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: key public run view on workspace, job and token Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
8bab579665 |
stop destroying AI sessions in workspaces reached without a usr row (#10567)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
c59b60c729 |
fix: keep the same_worker pin when a suspend ends without approval (#10552)
* fix: keep the same_worker pin when a suspend ends without approval A disapproved or timed-out approval gate hands the flow back through the UpdateFlow channel with unrecoverable = true. That flag means "the previous step's worker died", and it is read by six sites. Five of them happen to want what it does here, but continue_on_same_worker and continue_with_runners do not: the worker that ran the approval step is alive, so unpinning the error handler and routing it by tag breaks the ./shared contract of a same_worker flow and can land it on a worker group that cannot run it — the same defect #10551 fixed for the three producers that hand back a live flow. Replace the boolean with StepFailureKind so the suspend producer can say "worker alive, but this failure is not the module's to handle" instead of overstating a worker death. The failed module's error policy is deliberately still bypassed: the failure is recorded against the step the gate was holding back, which never ran, so its retry would re-open the gate and its continue_on_error would skip it outright (verified: the gated step is marked Failure with a nil job id and the flow jumps past it). suspend. continue_on_disapprove_timeout remains the way to continue past a gate. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat(flow-editor): flag that continue on error does not cover the approval gate A resolved approval is recorded against the step the gate holds back, not the step carrying the suspend, so continue_on_error never sees it: the flow still stops on a disapproval or timeout. Point users at suspend.continue_on_disapprove_timeout, which is what actually continues past a gate, whenever both settings are on and that one is not. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
1aee22296e |
fix: keep the same_worker pin across a flow module that spawns no job (#10551)
When a module completes without spawning a job — an empty branch, an empty for-loop, or a module already marked Success — the flow hands itself back through the UpdateFlow channel, and the result processor resumed it with unrecoverable = true regardless of what sent it. That flag means "the previous step's worker died", which holds for none of the three producers except a suspend that ended without approval. The stale argument was inert until continue_on_same_worker and continue_with_runners started reading it, since when the step after such a module is pushed as an ordinary queued job. It is then routed by tag and can land on any worker in the pool, breaking both the ./shared directory contract and the guarantee that a same_worker flow stays on a worker able to run it — a step whose tag resolves to a worker group that cannot execute its language fails instantly, taking the flow with it. Carry the flag on the UpdateFlow message so each producer states its own case, rather than having the shared receiver assume the worst. The three that hand back a live flow forward whatever their caller reported, so a genuinely unrecoverable failure still crosses the hop unchanged. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
ecae9320d0 |
fix: let admins edit the dev workspace lock ruleset (#10512)
* fix: let admins edit the dev workspace lock ruleset Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: route the empty protections panel through the owning workspace Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: make protection rule rename actually apply Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore: cache the renamed protection rule query for sqlx offline Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep verbatim rule names and scope parent-admin lookup to its workspace Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: store renamed protection rule names verbatim Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
d9dd036edc |
fix: stop app updates from silently converting an app between raw and low-code (#10495)
* fix: stop app updates from silently converting an app between raw and low-code * fix: lock the app row for the kind guard and route MCP away from raw apps * style: condense the restore kind-change comment |
||
|
|
fb82748296 |
fix: make on_behalf_of control permissions for scripts and flows (#10438)
* fix: make on_behalf_of control permissions for scripts and flows Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: inherit the recorded on-behalf-of identity when a preserving deploy omits it Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep an omitted permissioned_as from re-versioning an unchanged script Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: derive the on-behalf-of principal from the email and reject mismatched pairs Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: stop workspace deploys from carrying a source-workspace principal Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: correct the onBehalfOfPermissionedAs param doc Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test: pin that workspace deploys never carry a source-workspace principal Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: correct the omitted-principal contract and refresh generated prompts Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep external-superadmin principals on email-only redeploys Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: scope the recorded principal to its workspace and prefer real accounts Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: carry the recorded principal correctly through drafts and set-permissioned-as Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: sweep draft identity pairs on email change and offboarding Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: leave group identities alone when sweeping a user's email Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: treat only g/ without an email as a group, and match the offboard preview Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: stop the group guard from skipping rows with no recorded principal Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: state the group guard once instead of restating it Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor: make the permissioned_as the only stored on-behalf-of identity Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * perf: skip resolving the on-behalf-of address for sync clients that discard it Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: address the local review of the identity refactor Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: resolve the on-behalf-of identity coherently across clones, offboarding and no-op deploys * test: pin that a fork keeps only the on-behalf-of identities that resolve in it * fix: decide a principal prefix-first everywhere and canonicalize bare addresses * fix: prefix a slash-containing address so a reader cannot take it for a group * fix: read an address as a username before the group- convention * fix: rewrite the canonical principal when an account's address moves * fix: keep the address form of a principal to accounts without a usr row * fix: reject an identity a job row cannot carry and read it uncached at dispatch * fix: count characters against the job identity width and cap the backfill * refactor: name the script/flow principal on_behalf_of, as apps do * docs: state the caller-must-authorize contract on the identity resolvers * fix: keep writing on_behalf_of_email until every worker reads the principal * fix: err high on the compatibility version and document the last resolver * fix: keep the compatibility address current through identity mutations * fix: carry the compatibility address with the principal on every copy path * chore: re-pin the EE ref to the companion branch merged with EE main * fix: key the dbt retry lookup on the stored principal * fix: keep a mixed-version address recoverable through a fork * fix: read a round-tripped address uncached so a redeploy is not rejected * fix: refuse an email change that would make a principal unenqueueable * chore: update ee-repo-ref to ac3d7d015296f041ae44ab6bc4953485f44d36e4 This commit updates the EE repository reference after PR #704 was merged in windmill-ee-private. Previous ee-repo-ref: 219b0b03905a1a0028054b3a4985724e77d09036 New ee-repo-ref: ac3d7d015296f041ae44ab6bc4953485f44d36e4 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> |
||
|
|
dda59767c2 |
feat: stamp webhook trigger_kind on token-driven job runs (#10431)
* feat: stamp ui vs webhook trigger_kind on direct job runs * fix: gate ui trigger kind on min worker version and dedupe display names * docs: state that the ui trigger kind attributes rather than proves * refactor: fold the trigger fallback into one trigger_or_fallback helper * feat: hold trigger_kind as a tolerant label on the worker paths * chore: refresh the sqlx offline cache for the trigger_kind label queries * chore: update ee-repo-ref to 7de7daff5eed410e0c815ad6b292d2b4303f02f2 This commit updates the EE repository reference after PR #700 was merged in windmill-ee-private. Previous ee-repo-ref: 974ab910d9a30c5565e1198ee312acc6d11239f3 New ee-repo-ref: 7de7daff5eed410e0c815ad6b292d2b4303f02f2 Automated by sync-ee-ref workflow. * fix: keep the API job structs tolerant of unknown trigger kinds too * chore: point ee-repo-ref at the merged EE main --------- Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com> |
||
|
|
02c4a9e515 |
fix: carry the token label into job-run audit rows (#10433)
* fix: carry the token label into job-run audit rows * docs: state the audit end-user precedence at the push signature * chore: point ee-repo-ref at the companion branch * docs: state the username/end_user split at the push signature * feat: keep the audit caller searchable when a token label takes end_user * fix: skip the caller parameter when it repeats the end user |
||
|
|
68a52f45a7 | refactor: deprecate username_to_email in favor of WM_END_USER_EMAIL (#10429) | ||
|
|
c69f08073a |
fix: add apps:run to the token scope picker and confine path-scoped app tokens (#10428)
* fix: expose apps:run in the token scope picker and let apps:write grant it Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: confine path-scoped app run/write tokens to the app they name Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: let apps:run read back its own app's S3 files, condense scope comments Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: let apps:write mint apps:run and extend run read-back to app S3 display routes Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: drop stale embed-token wording from the app S3 helper summary Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
3716a71fd7 |
fix: credit the token owner instead of the token label in the audit trail (#10423)
* fix: credit the token owner instead of the token label in the audit trail Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: address review findings on token-owner audit attribution Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: carry token-label provenance explicitly instead of inferring it Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore: point ee-repo-ref at the companion branch Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: trust only non-forgeable token labels to name the acting entity Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: reject reserved system-token labels at token creation Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: narrow the token-label guard to server-minted namespaces Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: add the provenance field to the remaining ApiAuthed literals Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: stop trusting the email- label, which no mint produces Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
318c9f0073 |
feat(git-sync): dedicated base url for GitHub webhook delivery (#10411)
* feat(git-sync): let GitHub webhooks register a dedicated base url Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(git-sync): validate the webhook base url and apply it on change Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore: pin ee ref for the git-sync webhook base url change Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(git-sync): validate and reconcile the webhook base url on every write path Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(git-sync): route every declarative settings writer through the same rules Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(git-sync): let the reconciler own the webhook field write-back Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(git-sync): make the webhook base url validators agree across UI and server Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(git-sync): lock the workspace row across git_sync read-modify-writes Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test: pin the webhook base url validator to its server counterpart Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(git-sync): retry a failed webhook move on every re-apply of the setting Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(git-sync): retry pending webhook moves on every declarative re-apply Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(git-sync): reject non-string webhook base urls and bound the sweep Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(git-sync): reject credential-bearing webhook base urls Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(git-sync): keep credentials out of webhook base url validation errors Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(git-sync): redact through the last authority @ when reporting a bad url Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(git-sync): stop echoing unparsed webhook base urls instead of scrubbing them Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(git-sync): never echo a submitted webhook base url in validation errors Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(git-sync): keep the submitted scheme out of validation errors Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * refactor(git-sync): drop the webhook sweep, surface stale receivers in settings Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(git-sync): refresh the stale webhook list when settings are saved Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(git-sync): mark registered_url nullable and drop the duplicated field error Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore: pin ee ref after dropping the reconcile lock and CAS Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(git-sync): refresh the stale webhook list on category saves too Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore: update ee-repo-ref to aa05ca8e97fc8265cd724753a80db37f83243254 This commit updates the EE repository reference after PR #695 was merged in windmill-ee-private. Previous ee-repo-ref: 3e6cd9226b68707233ae2434511fe5131dce808b New ee-repo-ref: aa05ca8e97fc8265cd724753a80db37f83243254 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> |
||
|
|
fbf9f04e10 |
fix: surface the real postgres error when data table migrations fail (#10371)
* fix: surface the real postgres error when data table migrations fail Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: address review nits on the data table migration error fix Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: name the exact grant a data table migration needs Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: quote both identifiers in the data table grant hint Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * feat: add a data table connection and privilege check to workspace settings Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: report data table privileges from the capability fields, not the grant list Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: read grant targets from the server and drop the public schema guess Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: render the search_path suggestion server-side and pin the granted database Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: key the connection check on request identity, not the data table name Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: declare the data table check schema field nullable and required Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
8a96e3a4ec |
fix: raw apps with no stylesheet were permanently un-deployable (#10364)
* fix: raw apps with no stylesheet were permanently un-deployable Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: keep js strict when defaulting the raw app bundle css Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test: drop ephemeral narration from raw app bundle regression test Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test: pin the extension each raw app bundle half is fetched under Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
50da65c886 |
feat: show per-owner runnable counts in the homepage tree (WIN-2253) (#10351)
* feat: show per-owner runnable counts in the homepage tree Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: exclude pipeline members from runnable owner counts Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: address review findings on runnable owner counts Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: avoid tree reflow while counts load and label pipeline rows Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: drop collapsed owners' cached rows when the tree scope changes Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: untrack tree owners whose node is removed Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
4b7ab64a48 |
fix: enforce per-job authorization on cancel and force_cancel endpoints (#10341)
* fix: enforce per-job authorization on cancel and force_cancel Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: authorize force_cancel on the ancestor it actually kills Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix: fail closed when the force_cancel ancestor walk is truncated Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
dc5182f86c | fix: operators cannot see flows and apps on the homepage (#10340) | ||
|
|
9cef724ff2 |
feat: bind WAC approval urls to a named wait_for_approval step (#10317)
* feat: bind WAC approval urls to a named wait_for_approval step Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: reject duplicate WAC approval step keys instead of renaming them Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: reject WAC approval links minted for a step that is not awaiting approval Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: bind WAC approval links to the awaiting step and stop step key aliasing Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: reject empty approval keys and scope minted-key writes to the workspace Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: enforce WAC approval binding at consumption and reject colliding keys Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: make WAC approval binding and collision checks atomic, harden TS step keys Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: decrement WAC suspend atomically instead of from a pre-lock snapshot Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore: add sqlx cache entry for the atomic WAC suspend decrement Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: omit empty approver param from python get_approval_urls Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test: pin the suspend-snapshot decrement and the colliding-mint race Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test: drop the suspend-snapshot interleave test, it cannot both be stable and discriminate Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: reject step keys that cannot be minted as a URL path segment Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
71b7135cf2 |
feat: multiple homepage sort orders via an efficient merged runnables endpoint (#10297)
Adds recently-updated / oldest / name A-Z / name Z-A sort orders to the homepage (WIN-2236), produced server-side by a new merged, index-backed, keyset-paginated GET /w/{workspace}/runnables/list so a chosen order is globally correct across scripts + flows + apps and stays efficient on large workspaces.
- Backend: UNION ALL of script/flow/app ordered by index (Merge Append + LIMIT); keyset (sort_key, path, kind, tiebreak) cursor; per-branch LIMIT bounds correlated projections; starred-first pinning; RLS + scope-token filters in SQL. Archived view returns the latest row per path. Migration adds time + lowered-name indexes (built CONCURRENTLY).
- Frontend: server-side sort/kind/owner filters + hybrid search (instant client + on-demand server pagination); file-explorer tree with every folder and your user namespace as lazy-loaded top-level nodes (per-owner "Load more", nested subfolders, bounded "expand all", in-place re-sort without collapse or flicker); the client sorts by the server fetch ordinal to reproduce the endpoint's exact order; empty state distinguishes an empty workspace from too-narrow filters.
Reviewed clean by Claude and Pi (good to merge) and Codex (mergeable).
|
||
|
|
2143d45815 |
fix: WAC wait_for_approval reads its own approval result, not the first (#10314)
In prepare_checkpoint_for_resume the resume_job lookup took the oldest row
for the job (ORDER BY created_at ASC LIMIT 1), so a WAC workflow with
multiple sequential wait_for_approval() calls always read the first
approval's result for every step. Consumed rows are never deleted, so the
2nd and 3rd approvals inherited the 1st's result (all showed approved:true
even if the 2nd was cancelled and the 3rd timed out).
Track the resume_job row ids consumed by earlier approval steps in the
checkpoint (consumed_resume_ids) and exclude them, so each step reads its
own row. This is channel-agnostic and needs no clock reasoning:
resume_job.resume_id is only hash(step_key) for the inline resume URL; the
approval page, the in-run approve button, Slack, Teams and resume-as-owner
all store a random resume_id, so filtering by resume_id would drop those
approvals and return approved:false even for a legitimate approval. A
timed-out step matches no row and still falls to the else branch returning
{approved: false}.
Adds a regression test driving three sequential approvals (approved,
cancelled, timed-out) against Postgres.
Fixes WIN-2241
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
f02df7fc45 |
feat(monitor): make between-steps zombie flows hand-recoverable (#10287)
* feat(monitor): make between-steps zombie flows hand-recoverable When a worker is OOM-killed mid state-transition, the flow is reaped as a between-steps zombie (children all success, module still InProgress). We do not auto-recover (a re-driven transition can OOM again), so instead: - Append actionable recovery guidance to the cancellation reason when the reaped step's state is derivable (every child a success completion): which step, iterations completed, raise memory then restart-from-step (UI + API). - Restart-from-step now reuses a zombie step verbatim (InProgress with all children successful) and restarts from the next step, so no completed child re-runs; downstream steps re-derive its result from flow_jobs on demand. - Cast flow_status ::text in the reaper query: reading the jsonb column as Box<str> included the binary version byte and silently failed FlowStatus parsing (disabling the restart-not-yet-started branch since the v2 migration). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(monitor): only reuse a between-steps zombie step that provably finished Address review findings on the zombie-restart reuse path: - Require structural completeness (FlowStatusModule::is_between_steps_complete): a serial for-loop / branch-all reaped mid-fan-out has an all-success prefix but unrun remaining iterations, so the cursor must sit on the last element; while-loops are never derivable (continuation is a post-iteration condition). Parallel containers preallocate all children, so success alone is conclusive. Shared by the monitor guidance and the restart resolution. - Decline reuse when the step carries stop_after_if / stop_after_all_iters_if: those predicates decide whether downstream steps run, and reuse would bypass them; such a step re-runs instead. - Decline reuse when the zombie step is the last module (advancing past it lands on the failure step); it falls back to the existing re-run path. - Unit tests for is_between_steps_complete and an integration test asserting a mid-iteration serial-loop zombie is re-run, not reused. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(monitor): align zombie recovery guidance with restart eligibility Address CI review findings: - Exclude skip_if / suspend / sleep (not just stop predicates) from reuse via FlowModule::allows_zombie_reuse, so a skipped/suspend-armed step is never synthesized as Success (which would strand a restart waiting on an approval it never armed). - The reaper does not load the flow definition, so it cannot know whether restart will reuse or re-run a given step; reword the guidance to state both outcomes (reuse where derivable, re-run for the flow's last step or one carrying a stop/skip condition, approval, or sleep) instead of promising "no re-run". - Make the mid-iteration regression test exercise the cursor-completeness guard: a downstream step makes the loop non-final, so reuse is prevented only by the guard; a truncated loop result would then fail the assertion. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(monitor): never let zombie reuse swallow a nested restart request A nested restart (RestartedFrom.nested) descends into the restart step's child to re-run an inner step. For an eligible zombie BranchOne/Subflow the outer branch_or_iteration_n is None, so reuse fired, skipped the container, and the explicitly requested inner step never re-ran. Thread the presence of a nested chain into restarted_flows_resolution and decline reuse when set. Regression test added (RED without the guard: the nested target is reused instead of re-run). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(monitor): don't auto-requeue preprocessor zombies as unstarted flows The ::text parse fix re-activated the "hasn't started yet, restart it" branch, but its `modules[0] == WaitingForPriorSteps` check also matches a flow whose preprocessor is still InProgress (step == -1, first module waiting). Requeuing such a flow re-runs the preprocessor, duplicating side effects / repeating the OOM. Gate the branch on FlowStatus::is_not_yet_started, which also requires the preprocessor (if any) to be WaitingForPriorSteps. Unit-tested. Also drop the numbered procedural narration from the happy-path test comments. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(monitor): only emit restart guidance for restartable (deployed, top-level) flows The recovery guidance points operators at the run page's "Re-start from" button and the restart API, but both require a top-level deployed flow: a preview has no flow path (the button is hidden, the API 400s) and a subflow child restarts via its root, not itself. Gate the guidance on runnable_path IS NOT NULL AND parent_job IS NULL so previews/subflows keep the existing wording instead of being told to use a button/endpoint that isn't there. Verified end-to-end: a reaped preview gets no RECOVERY block, a reaped deployed flow does. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(monitor): gate recovery guidance on kind='flow' to match the restart surface Addresses review nit: a pathful editor preview (kind='flowpreview' with a runnable_path) satisfied the previous runnable_path check but the run page only renders the "Re-start from" button for kind='flow'. Match that condition exactly so previews/singlestepflow keep the plain wording. Verified end-to-end: a reaped pathful preview now gets no RECOVERY block. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(monitor): disable zombie reuse for raw-flow (editor preview) restarts A JobPayload::RawFlow restart queues the request's current, possibly EDITED, definition, but restarted_flows_resolution validates reuse against the completed job's STORED definition. For an eligible preview zombie, editing the restart step and restarting from it would synthesize Success from the old children and skip the edit. Thread allow_zombie_reuse into the resolver (true only for JobPayload::RestartedFlow, which queues the stored definition) and decline reuse for raw-flow restarts. Regression test added (RED without the guard: the edited step is skipped and the old result is reused). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(sqlx): add offline cache for zombie_flow_recovery test queries The integration test's UPDATE v2_job_completed queries had no .sqlx entry, so the CI SQLX_OFFLINE build of the test failed to compile. Regenerated with --all-targets --features deno_core,quickjs to capture the test-target queries. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(monitor): drop procedural narration from the raw-flow zombie test Per AGENTS.md (comments record constraints, not narration): remove the two step-describing comments the reviewer flagged; the test doc comment already carries the durable rationale. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(monitor): restrict zombie reuse to monitor-reaped flows The reuse predicate matched the InProgress/all-children-success shape without checking provenance, so an ordinary force-cancel at the same boundary (a child succeeded before its parent transition landed) would also be reused, dropping the usual restart-from-step re-run. Gate reuse on canceled_by = 'monitor' (the username the zombie reaper cancels with). Regression test added (RED without the guard: a user-cancelled flow reuses the child instead of re-running it). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(monitor): reuse zombie step on Some(0) too, so the run-page button works The run page's "Re-start from" button always sends branch_or_iteration_n = 0 (never omits it), but reuse only fired for None, so the exact UI path the recovery message points to would re-run the children instead of reusing them. Treat a whole-step restart (None or Some(0)) as reuse-eligible; Some(n>=1) keeps the explicit partial-container restart. Verified against the live EE restart API with branch_or_iteration_n=0: all loop-iteration child UUIDs are reused. Happy- path test now sends Some(0) to match the button. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
c50a2abad0 |
fix(jobs): sanitize NUL in completed job result before jsonb insert (#10274)
## Summary
A job whose result contains a real NUL (U+0000) serializes to a `\u0000` JSON escape that the `jsonb`-typed `v2_job_completed.result` column rejects with Postgres `22P05` ("unsupported Unicode escape sequence"). This aborts the `INSERT` in `commit_completed_job`, which then retries 10 times and leaves the job unable to complete (surfaced as `Could not add completed job <id>: ... unsupported Unicode escape sequence`).
The fix sanitizes the serialized result immediately before the insert, with effectively zero overhead on the common NUL-free path.
## Changes
- **Promote `strip_json_nul` into `windmill-common`** (`utils.rs`): `fn strip_json_nul(&str) -> Cow<str>` — a `contains("\\u0000")` fast guard returns the input borrowed when clean; only a genuine odd-parity NUL escape triggers the O(n) rebuild. `Cow::Owned` is returned **only** when a NUL was actually stripped, so a legitimate `\\u0000` (escaped backslash + literal text) borrows through untouched. Replaces the two duplicated copies previously in `windmill-api/src/drafts.rs` (`strip_json_nul`) and `windmill-api/src/apps.rs` (`strip_null_chars`); both call sites now use the shared helper.
- **Add `serialized_json()` to the `ValidableJson` trait** (`windmill-queue/src/jobs.rs`): `Box<RawValue>` returns `Cow::Borrowed(self.get())` (zero-cost, already serialized); other impls serialize on demand via `to_raw_value`.
- **`commit_completed_job`** binds `strip_json_nul(result.serialized_json())` as `$3::text::jsonb` in both the `INSERT ... SELECT` and the `ON CONFLICT ... result = $3` (was `result as Json<&T>`). Stored data is unchanged (Postgres parses JSON text into `jsonb` identically); `wm_labels`/`result_metadata` still operate on the typed `T`.
- **Regenerated the sqlx offline cache** (one query file swapped; EE caches preserved).
- **Doc:** updated the stale `strip_null_chars` reference in `windmill-api-workspaces/src/workspaces.rs` to point at the shared `strip_json_nul`.
## Test plan
- [x] `cargo check -p windmill-queue -p windmill-api -p windmill-common -p windmill-api-workspaces` — clean, no warnings
- [x] `strip_json_nul` unit tests in `windmill-common` (clean-borrow, real-NUL, legit-escape borrow no-op, collision, nested keys/values, odd-run): 6 passed
- [x] End-to-end regression in `backend/tests/nativets_jobs.rs` (`--features deno_core`): a JS job returning a genuine NUL and a literal `\\u0000` completes, storing `"ab"` (stripped) and `"a\\u0000b"` (preserved). Without the fix the insert aborts and the job never completes.
- [x] `backend/tests/drafts_nul.rs` integration test still passes (helper refactor intact)
|
||
|
|
2d24b3ac49 |
fix(jobs): enforce self_approval_disabled on the UI resume path (#10262)
* fix(jobs): enforce self_approval_disabled on the UI resume path The "Resume" button in the run detail UI calls the resume_suspended endpoint, whose owner shortcut skipped the approval-condition checks entirely. A flow owner/operator who triggered the run could therefore self-approve despite self_approval_disabled, unlike the owner endpoint which enforces it. Only admins should bypass self-approval. - Extract require_not_self_approval and enforce it before the owner shortcut in resume_suspended and can_approve_step (button visibility), matching resume_suspended_flow_as_owner. - Persist approval_conditions when self_approval_disabled is set even without user_auth_required, so the restriction is not silently dropped at the resume boundary for raw-flow/CLI authors. Fixes WIN-2223 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(jobs): keep self-approval capability-based on the secret path; docs/tests Scope the self_approval_disabled enforcement to identity-based resume boundaries only. Possession of the full HMAC resume URL is the authorization on the secret path (the URL is disclosed only to intended approvers, e.g. when a step returns it), so resume_suspended_job intentionally keeps skipping approval conditions and token-only (anonymous) resumes on resume_suspended are not gated either. The logged-in owner/operator self-approval fix stays. - Add extract_approval_conditions helper (WAC vs classic) reused in resume_suspended. - Update can_approve_step doc to reflect that self_approval_disabled bars the triggerer before the owner shortcut (codex nit). - Reword new test comments to state the invariant, not prior behavior (codex nit). - Add test_self_approval_disabled_without_user_auth_required covering the persistence + authenticated self-approval check for a non-owner triggerer. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
4a898247a2 |
fix(apps): let entitled viewers read pre-existing S3 files from deployed apps (#10245)
* fix(apps): let entitled viewers read pre-existing S3 files from deployed apps Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(apps): confine S3 viewer fallback to full unscoped sessions A scope-restricted token (e.g. apps:read:<app>, or an app-embed token) is allowed on apps_u/* but rejected by the route-scope middleware on job_helpers/*, so granting it the viewer fallback would be a new capability it cannot obtain directly. Gate the fallback on scopes.is_none() so only full sessions (which can already read via job_helpers) delegate; scoped and anonymous callers stay gated. Add a scoped-token isolation assertion. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(apps): treat filter-tags-only tokens as unscoped for S3 viewer fallback The scopes.is_none() guard wrongly denied the viewer fallback to tokens that are effectively unscoped (empty scope arrays and if_jobs:filter_tags:-only tokens), which the route-scope middleware treats as unrestricted and which can therefore read the same file via job_helpers directly. Reuse that semantics via a shared is_effectively_unscoped helper so the relaxation covers exactly the tokens that gain no new capability, while genuinely scoped tokens stay gated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
39058c0a01 |
fix(postgres-triggers): enforce resource-path scopes on ancillary routes (#10222)
* fix(postgres-triggers): enforce resource-path scopes on slot/publication routes
The Postgres trigger ancillary routes (slot management, publication
management, version/logical-level checks, template script creation) relied
only on the route-level middleware, which validates the scope domain+action
but delegates resource-path enforcement to each handler. These handlers made
no check_scopes call, so a token scoped to one postgres resource path (e.g.
postgres_triggers:write:u/alice/*) could drive these endpoints against any
postgres resource in the workspace, including the destructive drop_slot_name
(pg_terminate_backend + pg_drop_replication_slot).
Add a check_scopes call at the top of each affected handler, before any
connection is opened, mirroring the generic trigger CRUD handlers. Read
endpoints require postgres_triggers:read:{path}; write endpoints require
postgres_triggers:write:{path}.
Fixes WIN-2213
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: condense postgres_trigger_scope test module comment
Address Codex nit: state the durable constraint (mismatched scope must fail
before DB access) instead of narrating pre-fix behavior and change history.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
2ff5a918d5 |
feat(worker-tags): add * fork marker to workspace-scoped custom tags (#10177)
* feat(worker-tags): add `*` fork marker to workspace-scoped custom tags Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(worker-tags): pin fork-marker tag admission through real lineage Also render WorkspaceMatcher in its authored form in the operator-facing "not in the allowed CUSTOM_TAGS" error, and correct the authorization note on workspace_with_fork_ancestors. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(worker-tags): gate fork-lineage tag lookup on workspace membership exists_workers_with_tags is a global route whose workspace is a query param. Resolving its fork lineage for a caller who is not a member disclosed whether an arbitrary workspace descends from one named by a tag(parent*) rule. Require membership before the lineage lookup, and sharpen the workspace_with_fork_ancestors contract accordingly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(sqlx): add offline cache for fork-marker test INSERT query The new test_fork_marker_tag_admission_through_lineage integration test inserts a fork workspace with a bind-parameterized query that had no cached entry, failing the SQLX_OFFLINE=true CI build. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
8c725d9e44 |
fix(apps): honor presigned S3 signature on app display/preview routes (#10141)
The app provenance gate short-circuits on a valid presigned signature, but only the raw download_s3_file route parsed it. The parquet/csv/table-count/file-preview/metadata routes discarded sig/exp and always fell through to the provenance gate, so a presigned S3 object rendered as a table showed "File restricted" for any viewer who did not produce it. Thread sig/exp through every apps_u S3 display route and forward the presigned bearer from ParqetCsvTableRenderer/DisplayResult. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
f7eb5c460d |
fix(apps): invalidate cached app policy on change or deletion (GHSA-r5v4-cxh9-7qhq) (#10121)
* fix(apps): invalidate cached app policy on change or deletion (GHSA-r5v4-cxh9-7qhq) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(agents): keep PR tests and comments minimal and non-ephemeral Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
188647a942 |
fix(security): enforce variables:write scope on resource-delete var cascade (GHSA-xmr2-98m6-cjf7) (#10123)
A token scoped only to resources:write:<path> could delete linked secret variables it had no variables:write scope for, by embedding $var:<victim> in an attacker-controlled resource value and triggering the resource-delete cascade. #9712 re-enforced scoped-token boundaries broadly but missed this path. Add check_linked_var_delete_scopes, called before the cascade in both delete_resource and delete_resources_bulk: require variables:write for every linked variable, failing (and rolling back) the delete otherwise. No-op for unscoped tokens, so full-token cascade cleanup is unchanged. No co-located-path exemption: a resource and a variable may share a path, and a resource-write token can create a resource over an existing standalone variable and self-reference it, so "same path as the deleted resource" is attacker- forgeable and cannot stand in for variable scope. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
710a13a59d |
fix(apps): cover script/flow component outputs in deployed-app S3 provenance gate (#10070)
* fix(apps): cover script/flow component outputs in deployed-app S3 provenance gate Deployed apps read S3 files on-behalf of the app author for logged-in viewers (#10048). A confused-deputy guard confines those reads to files the app "produced", but the recent-production check only matched inline `appscript`/ `preview` jobs nested under the app path. Files produced by the deployed script/flow components an app is wired to run (e.g. a SQL query persisted to S3) were therefore denied "File restricted" for every viewer, admins included. Expand the provenance check to also match completed `script`/`flow`/`flowscript`/ `flownode` jobs whose `runnable_path` is one of the app's declared triggerables, and accept the author identity via `permissioned_as = on_behalf_of` (not only `created_by = caller`) so files produced on-behalf of the author are covered. Reads outside the app's declared triggerables stay denied. Adds a regression test seeding a script-kind produced file that reproduces the "File restricted" denial before the fix and passes after. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(apps): key S3 provenance on on-behalf identity + cover flow steps (review) Addresses the CI review on the S3 provenance gate: - P1 (confused deputy): the recent-production check keyed on `created_by = caller`, so a viewer who can run a declared script/flow directly (outside the app, with un-pinned inputs) could craft a result naming an author-only key and read it back through the app as the author. Key provenance instead on the producing job's `permissioned_as` matching the on-behalf identity the download reads as (the author in author-mode); a viewer's direct run has `permissioned_as = viewer` and no longer clears the gate. Drops `created_by` from both the appscript/preview and script/flow branches, closing the same latent hole in the pre-existing inline-script branch. - P2 (dead flow-step branch): `flowscript`/`flownode` jobs have `runnable_path = <flow_path>/<step_id>`, which exact `= ANY(...)` never matched. Split script vs flow triggerable paths; flow kinds now match the flow's own job (bare path) and its step jobs via a `<flow_path>/%` prefix, bounded to declared flows. - P2 (test realism): the regression test now uses the production component-prefixed triggerable key format (`<id>:script/...`), exercises a flow-step-produced key, and asserts a viewer's own direct run of a declared script stays denied (the P1 case). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(apps): tie deployed-app S3 provenance to an app-origination marker (review) Second CI-review round flagged that `permissioned_as` still does not prove a job was app-launched: a runnable configured with its own `on_behalf_of` makes a direct `/jobs/run` resolve `permissioned_as` to that identity (the app author), so a viewer with run access could execute a declared runnable directly, craft an S3 result, and read it back through the app. The flow-path `LIKE fp || '/%'` match also let `_`/`%` in a declared path admit unrelated flows. Introduce a real app-origination marker instead of inferring provenance: - Add `JobTriggerKind::App`; `execute_component` stamps every app-launched job with `trigger_kind = 'app'` + `trigger = <app path>`. A direct `/jobs/run` cannot set this, so it is the authoritative signal that a file was produced *by the app*. - The provenance gate's recent-production check collapses to `trigger_kind = 'app' AND trigger = <this app path>` (+ the 3h window and result containment). This drops the forgeable `created_by`/`permissioned_as`/ `runnable_path`/kind logic entirely and removes the `LIKE` wildcard issue. - Provenance is scoped to THIS app's path, so another app's jobs (even same author) do not authorize this app's reads. Regression test rewritten to the marker model: an app-produced key clears for viewer and admin; a direct run whose `permissioned_as` resolves to the author stays denied (the forgery); another app's output stays denied. Adds `app` to the OpenAPI JobTriggerKind enum. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(apps): assert execute_component stamps trigger_kind='app' at runtime Adds an end-to-end test that runs a real script component through the app runtime (`apps_u/execute_component`) and asserts the enqueued job carries the app-origination marker `trigger_kind = 'app'` + `trigger = <app path>` (not the runnable path). The provenance-gate tests seed the marker directly; this proves the runtime actually produces the exact marker the gate depends on. execute_component commits the job row and returns its id, so the assertion reads the row directly — no worker needed to run the job. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(triggers): reject trigger_kind=app for suspended-job reassignment (review) `JobTriggerKind::App` (added for the app-origination S3 marker) became a valid value for the resume/cancel suspended-trigger routes, whose handler derives the table name `<kind>_trigger`. There is no `app_trigger` table, so both endpoints would fail with a missing-relation database error (500). Reject `App` in `get_suspended_trigger` alongside webhook/schedule so it returns a clean 400. Adds a regression test asserting the reassignment route returns 400 (not 500) for trigger_kind=app. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(apps): don't stamp app-origination marker on preview runs (review) The app-origination marker (trigger_kind='app') was stamped unconditionally, including preview mode. A preview lets a `jobs:run` caller supply arbitrary `raw_code` against ANY app path without that app's deployed policy (raw_code with no path/id skips all app authorization), so a preview returning `{"s3":"<author-only-key>"}` would forge the exact marker the S3 provenance gate trusts and read the victim app author's file. Gate the marker on `!is_preview`: only deployed, policy-checked executions are app-provenanced. Preview/editor S3 display does not rely on this marker (the editor routes reads through the force_viewer allowlist), so nothing legitimate regresses. Adds a regression test asserting a preview run's job is not stamped trigger_kind='app'. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(apps): editor-authorize preview marker + per-viewer S3 provenance isolation (review) Closes the codex P1 (preview forgery) without breaking editor preview downloads, and adds cross-viewer isolation to the provenance gate. - Preview marker now requires app write: `execute_component` stamps the app-origination marker on a preview only when the caller can EDIT that app (`require_is_writer`), instead of never stamping previews. An app editor already wields the app's author identity (they can deploy a component that reads the same file), so marking their own preview is no escalation and keeps preview-produced S3 results downloadable in the editor; a `jobs:run`-only caller who cannot edit the app still cannot forge the marker. Deployed runs are unchanged (always marked). - Per-viewer isolation: the provenance gate now also requires `j.created_by = <this caller>`. The security boundary stays the un-forgeable `trigger_kind='app'` marker; `created_by` is an additional filter ANDed under it, so it only narrows — a viewer can only download keys their OWN app runs produced, not another viewer's result. Restores the per-caller scoping #10048 had, now safe on top of the marker. Tests: preview marked iff caller can edit the app; cross-viewer isolation (another viewer's app-marked key denied, no admin bypass); direct-run and other-app keys still denied; deployed run still stamped. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(apps): require apps:write scope (not just writer ACL) to mark preview provenance (review) require_is_writer checks the user's underlying ACL but ignores token scopes, so a writer's token deliberately scoped to apps:run/apps:read/jobs:run but WITHOUT apps:write could still mark a preview and forge provenance — even though that token cannot deploy the app (update_app requires apps:write), breaking the "any marked caller can deploy equivalent code" rationale. Require BOTH apps:write:<path> scope (check_scopes) AND the writer ACL (require_is_writer) before stamping a preview's app-origination marker. Deployed runs unchanged. Adds a scope-restricted-writer token to the test (apps:run/read + jobs:run, no apps:write) and asserts its preview stays unmarked; retains the full-editor positive case and the non-editor negative case. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(apps): never app-provenance preview runs; read editor S3 as the caller (review) Simplifies the preview handling: a preview executes as the *caller* (Viewer mode), never as the author, so its results must be read back as the caller — never author-mode — and must never carry the app-origination marker. This removes the whole `require_is_writer` / `apps:write` / `can_preserve_on_behalf_of` reasoning (which was also unsound: a writer's token or session may not be able to deploy a component running as the app's on-behalf identity, so marking their preview could still escalate). - Backend: mark the app-origination marker for deployed runs only (`!is_preview`). - Frontend: `getS3File` (AppImage/AppPdf/AppDownload) now routes editor/preview reads through the viewer-scoped `job_helpers/download_s3_file` endpoint (reads as the caller), matching what DisplayResult/ParqetCsvTableRenderer already do; only a deployed app view uses the provenance-gated `apps_u` endpoint. This is the path that previously relied on marking previews, so nothing regresses. Test: a preview is never app-provenanced (owner's own preview and a non-editor's both stay unmarked). Cross-viewer isolation, deployed marking, and the reassignment guard are unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(apps): app components run on-behalf of the app, not the referenced runnable (review) Root-causes codex's on-behalf-preview finding: `execute_component` was overriding the app's resolved on-behalf identity with the referenced script/flow's OWN `on_behalf_of` (its `on_behalf_of_email`). That is wrong in the app context — the app's execution mode should govern: - A Viewer-mode app could execute a component AS the referenced runnable's on_behalf identity (privilege confusion / escalation), instead of as the viewer. - A preview would run as that identity rather than as the caller, so its S3 output could not be read back as the caller — the download-identity mismatch codex flagged. Always use the app-resolved identity (author in author-mode, caller in viewer/preview); a referenced runnable's own `on_behalf_of` no longer leaks into app execution. Direct `/jobs/run` still honors a runnable's `on_behalf_of` (unchanged). With this, previews always run as the caller, so reading editor/preview S3 as the caller (viewer-scoped `job_helpers`) is unconditionally correct. - Test: the deployed-component e2e now seeds the script with a distinct on_behalf and asserts the component job's `permissioned_as` is the app identity, not the script's. - Also reword the getS3File `configuration` param comment to describe current state only (AGENTS.md comment rule). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(apps): surface 'app' trigger kind in Runs UI; condense provenance comments (review) Addresses codex review nits: - Add `app` to `jobTriggerKinds`, `triggerIconMap` (LayoutDashboard), and `triggerDisplayNamesMap` so app-component jobs (which now carry `trigger_kind = 'app'`) are filterable in Runs and render their trigger info. - Condense the app-origination marker, on-behalf-identity, and provenance-gate comments to state each invariant once in <=4 lines at its relevant site (AGENTS.md comment rule). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
1e192f2d86 |
feat(apps): authorize deployed-app S3 reads on-behalf of the author for logged-in viewers (#10048)
* feat(apps): authorize deployed-app S3 reads on-behalf of the author for logged-in viewers A logged-in user viewing a deployed app now reads S3 files (rich result, table/image/PDF preview, CSV export, download, metadata) the same way an anonymous viewer already does: on-behalf of the app author per the app policy's execution_mode, gated by an app-provenance check — instead of against the viewer's own S3 permissions. This aligns S3 with every other thing an app does (scripts, flows, resources all already run on-behalf of the author) and lets an operator who lacks folder S3 permission still see data rendered inside the app. The raw job_helpers/* S3 API stays viewer-scoped: a viewer who lacks folder permission is still denied there. Only which endpoint the app frontend uses for logged-in deployed viewers changes. Backend: - Add app-scoped, provenance-gated apps_u/* variants for all S3 display ops (download_s3_file already existed; add download_s3_parquet_file_as_csv, load_file_metadata, load_file_preview, load_parquet_preview, load_csv_preview, load_table_count). Each routes through one shared helper (app_s3_on_behalf_and_provenance) that scope-confines an app embed token, resolves the on-behalf identity, and runs the provenance gate ONCE before dispatching to the EE *_internal S3 helpers. - Close the confused-deputy hole in check_if_allowed_to_access_s3_file_from_app: the unconditional Ok() bypass for a logged-in, non-embed session now only applies in viewer execution mode (where the on-behalf identity IS the viewer, so the viewer's own permissions still bound the read downstream). Author-mode reads (anonymous/publisher) always enforce provenance, for anonymous and logged-in viewers alike, so a viewer cannot launder the author's S3 permissions with an arbitrary file_key. Frontend: - Route the deployed-app view through apps_u/* using the app-viewer isEditor signal instead of login state (the old $userStore proxy wrongly sent logged-in deployed viewers to the viewer-scoped job_helpers API). Editor and preview keep viewer identity via job_helpers. execution_mode: viewer remains the escape hatch for per-viewer S3 enforcement. Fixes provenance-gated S3 display for logged-in operators on deployed apps. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(backend): document cargo features, restarting the dev backend, and filesystem object storage The dev backend runs `cargo watch --features quickjs` by default, which omits S3, EE, MCP, and non-JS runtimes — feature-gated routes then 404 or return a "requires <feature>" stub at runtime. Add a backend/CLAUDE.md section that: - explains that you must restart the backend with the appropriate features to exercise gated functionality, with the pid/cwd-scoped restart recipe (never pkill target/debug/windmill) and the PORT=$BACKEND_PORT gotcha; - documents what each commonly-toggled feature gate does (private, enterprise, license, parquet, duckdb, language runtimes, mcp, trigger kinds, no_auth) plus common combinations; - documents using the built-in FilesystemStorage large-file storage for dev workspace object storage (hidden from the UI dropdown; set via edit_large_file_storage_config), including the advanced_permissions shape. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(apps): don't flatten inner query in app-scoped S3 preview routes axum's `Query` uses `serde_urlencoded`, which cannot deserialize the typed (numeric/bool) fields of a `#[serde(flatten)]`-ed struct and 400s on `limit` / `offset` ("invalid type: string, expected u32"). The app-scoped load_csv_preview / load_parquet_preview / load_table_count routes flattened LoadPreviewQuery / LoadCountQuery, so their previews were broken. Restate the fields directly on the outer query structs (with an into_inner() to rebuild the inner query) and extend the CE OSS stub to match. Also bumps ee-repo-ref.txt for the companion EE csv-separator panic fix. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: address CI review — nested DisplayResult routing, byte-range contract, docs, tests - [P1] Thread `appPath` into the nested `DisplayResult`s (render_all children and the expanded-result drawer) so logged-in deployed viewers route nested/expanded S3 tables, images, PDFs, and downloads through `apps_u/*` too, not job_helpers. - [P2] Mark `read_bytes_from`/`read_bytes_length` required on the `apps_u/load_file_preview` route (they are non-optional in LoadFilePreviewQuery), and mirror the full query shape in the CE OSS stub so the byte-range contract is enforced identically on CE and EE. - [P2] Fix the backend retrigger command in backend/CLAUDE.md: cargo watch runs from `backend/`, so `touch README.md` (not `backend/README.md`). - [P2] Trim app_s3_onbehalf.rs comments per AGENTS.md (state the invariant once, no drafting-history narration). - Extend the integration test to cover the table-count, csv-preview (numeric limit/offset deserialization), and file-preview (byte-range required) routes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(apps): tighten S3 provenance-gate comments per AGENTS.md Consolidate the viewer-mode / author-mode rationale to ≤4 lines at each branch of the gate, and drop the repeated explanation from the shared app_s3_on_behalf_and_provenance doc comment (which now just states what the helper does). No behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: update ee-repo-ref to f292a1040da6a667ce7c22abf63ec0debfdd480f This commit updates the EE repository reference after PR #657 was merged in windmill-ee-private. Previous ee-repo-ref: a582389084eb363997cb5e8053f29220e0d3eaec New ee-repo-ref: f292a1040da6a667ce7c22abf63ec0debfdd480f Automated by sync-ee-ref workflow. --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com> |
||
|
|
5844c32ac5 |
fix: enforce read authorization when signing S3 objects (#10049)
`sign_s3_objects` minted a long-lived HMAC bearer signature for any S3 key handed to it, by any authenticated workspace member, with no check that the caller was allowed to read that key. Since `validate_s3_signature` only verifies the HMAC and expiry at fetch time, any member (operators included) could mint a transferable capability to read arbitrary S3 keys, bypassing the advanced S3 permission rules (`check_lfs_object_path_permissions`). Authorize the read at mint time: add an `ApiAuthed` extractor and, before signing each key, require the caller's own `S3Permission::READ` via `get_workspace_s3_resource_and_check_paths`. A caller can no longer sign a key they cannot themselves read. The fetch-side validators are left unchanged. The only legitimate caller is the wmill SDK invoked from an app-author job, whose token authenticates as the executing (author) identity — which can read the key — so authorized app display is unaffected. Adds an integration test proving an authorized caller can sign a readable key (and the signature validates end-to-end through the presigned fetch route) while an unauthorized caller is refused. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
ab38e1418e |
fix: keep agent-worker server job-completed processor alive & self-healing (#10033)
* fix: keep agent-worker server job-completed processor alive on init-script failure The agent-worker API server's background job-completed processors relay completions on behalf of many remote agent workers. The processor loop exited (dropping its receiver) on an init-script failure, but on the server that failed init script belongs to a remote worker, not the server. Once enough processors exited, the shared completion channel disconnected and every /send_result POST returned 500, stranding completions and creating zombie-job restart loops. Add an is_agent_server flag so server relay processors don't self-terminate on init-script failure. Pins the EE companion change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: bump EE ref for send_result wait-for-processor change Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test: agent-worker server survives a failed init script End-to-end regression for the agent-worker-server processor bug: an agent worker runs a failing init script, POSTs the failed init-script completion to /send_result, and the test asserts the server's background job-completed processor stays alive (a subsequent job completes and no bg-processor critical alert is raised). Fails if the is_agent_server guard is removed (the processor breaks, the supervisor raises a critical alert). Requires --features enterprise,license,private,agent_worker_server. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test: replace heavyweight init-script e2e with focused unit tests The panic/respawn/alert and 503 timeout paths are now covered by fast, deterministic unit tests in windmill-api-agent-workers (supervise_processor, classify_send). Drop the enterprise-only, global-config-mutating e2e in favor of those. Bump EE ref. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: update ee-repo-ref to bc45d9275d4307132927dc8ad3e82049b1aed463 This commit updates the EE repository reference after PR #653 was merged in windmill-ee-private. Previous ee-repo-ref: 2aca03f28bb37e938ae548b81f1620b2e00dc0f7 New ee-repo-ref: bc45d9275d4307132927dc8ad3e82049b1aed463 Automated by sync-ee-ref workflow. * chore: bump EE ref for bg-processor alert rate-limiting Picks up windmill-ee-private#654: exponential backoff + rate-limited critical alerts in supervise_processor, addressing the code-review nit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: update ee-repo-ref to d48c0e01e8601a372353c032dd237ddb6fa3bbad This commit updates the EE repository reference after PR #654 was merged in windmill-ee-private. Previous ee-repo-ref: f89eeb6e333614850ef650e7df78e3c2335f107c New ee-repo-ref: d48c0e01e8601a372353c032dd237ddb6fa3bbad Automated by sync-ee-ref workflow. * chore: bump EE ref for graceful-shutdown-during-backoff fix Picks up windmill-ee-private#655: supervise_processor re-checks shutdown before respawn and selects on the shutdown broadcast during backoff, so a crash-loop backoff can't hang graceful shutdown. Addresses the Codex P1. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: update ee-repo-ref to 9bc5dfb9ce73a2d9b981a1de86eea6aa26688b79 This commit updates the EE repository reference after PR #655 was merged in windmill-ee-private. Previous ee-repo-ref: 8dc3b3d9ec8f9c28b227d36c2a1327b4b2017665 New ee-repo-ref: 9bc5dfb9ce73a2d9b981a1de86eea6aa26688b79 Automated by sync-ee-ref workflow. --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com> |
||
|
|
927b8d064f | fix: clear old path asset usage when renaming a script (#9979) | ||
|
|
5d7fb6deca |
feat(pipelines): asset freshness — fresh/stale badge (CE) + watchdog (EE) (#9909)
* feat(pipelines): passive asset freshness tracking on the graph Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore(pipelines): drop dead freshness-enforcement stub, document query ordering Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(pipelines): freshness watchdog (EE) — auto re-run stale producers Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(pipelines): watchdog review fixes — archived workspaces, badge kind parity, scan index Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(pipelines): CI review — no singlestepflow in freshness, +N parity, completion-time fallback Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(pipelines): CI review — history completedAt, freshness/asset trigger UI metadata Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: update ee-repo-ref to 6f5fe0f7f56696fbef5a8349da38496c32e71666 This commit updates the EE repository reference after PR #643 was merged in windmill-ee-private. Previous ee-repo-ref: 1f13380354bf591ae25a2c20d36917534bcc5459 New ee-repo-ref: 6f5fe0f7f56696fbef5a8349da38496c32e71666 Automated by sync-ee-ref workflow. --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com> |
||
|
|
af36498432 |
feat(pipelines): record upstream snapshot ids on cascade-dispatched jobs (#9910)
* feat(pipelines): record upstream snapshot ids on cascade-dispatched jobs Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor: batch upstream-snapshot lookup and memoize per subscriber Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
d15033cde6 |
fix: invalidate bun bundle cache on transitive relative-import changes (#9891)
* fix: invalidate bun bundle cache on transitive relative-import changes Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: do not memoize transient fetch errors in bundle-key import cache Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: use regular comment on lazy_static block (deny unused_doc_comments) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: align bundle-key import version selection with loader content endpoint Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
76a9523009 |
feat: use derived username instead of email for non-member superadmins (#9857)
* feat: use derived username instead of email for non-member superadmins Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: address review - drop redundant username cache, guard whoami membership by email Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor: use explicit non_member boolean instead of role string for superadmin banner Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: resolve email from password table for non-member superadmin permissioned_as Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: resolve non-member superadmin drafts via shared username->email resolver Adds resolve_username_to_email (usr, then super_admin password fallback for both derived-username and email modes) and uses it in get_email_from_permissioned_as and the drafts get/list endpoints, so a non-member superadmin's drafts resolve and no email leaks into the drafts payload. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test: superadmin-not-in-workspace schedule uses derived username as permissioned_as Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: resolve non-member superadmin identity in draft owner-circles, username_to_email, and home filter Applies the password-fallback username resolution to the script/flow/app/draft owner-circle subqueries and the username_to_email endpoint (was an admins-workspace 'username == email' hack), and switches the home items-list user-folder filter to the non_member flag instead of the now-broken username-contains-@ heuristic. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: backfill non-member superadmin favorites from email to derived username Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: propagate DB errors in username resolution instead of leaking email (CI review) Addresses cubic-dev-ai P2: get_instance_username_or_fallback_to_email now returns Result and only falls back to the email for a genuine 'no derived username'; a query error propagates so callers fail closed rather than leaking the raw email as the acting username. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: clarify non-member superadmin popover (username used + admin permissions) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: keep username_to_email endpoint member-only to not disclose non-member superadmin email (CI review) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: forbid disabling automate_username_creation once usernames assigned (CI review) Makes the setting effectively one-way once instance-wide usernames exist, so the global-uniqueness invariant that keeps stored u/<username> identities (schedules/triggers/drafts/superadmin ownership) unambiguous can never be dropped back to workspace-local uniqueness. Re-saving false on an already-disabled instance stays a no-op. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
40110bc715 |
fix: skipped suspend step no longer parks the flow forever (#9821)
* fix: skipped suspend step no longer parks the flow forever A flow step that declares a `suspend` (approval) but is skipped via `skip_if` was leaving the flow stuck waiting for a resume that would never arrive. Suspend gates the *next* step: before pushing step N, `needs_resume` checks whether step N-1 declared a non-zero `suspend` and finished as `Success`. A step skipped via `skip_if` is also recorded as `FlowStatusModule::Success` (with `skipped: true`), so `needs_resume` treated a skipped approval gate as a real one and parked the flow waiting for an event that nothing ever sends — until the suspend timeout (up to 24h). The skip is most visible when the skipped suspend step is followed by a branch/subflow: the flow appears stuck on the *following* predicate node with a generic resume button, while none of the branch/subflow steps ran. Fix: honor the `skipped` flag in `needs_resume` and do not gate the next step on a suspend that was skipped. Adds regression test `skipped_suspend_step_does_not_block_next_step` (times out without the fix, completes with it). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: reword regression test comment as a current invariant Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
1c4bb8af14 |
test: de-flake asset-dispatch by bypassing cross-DB script-hash caches (#9820)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
0dbd9c1231 |
perf: eliminate dual-connection DB pool contention across worker, queue, and api (#9798)
* perf: eliminate dual-connection DB pool contention across worker, queue, and api Reuse the held transaction (or move pool reads before begin()) instead of checking out a second pool connection while a tx is open, extending the fix from #9789/#7861. Targets the per-worker pool (max 5) hot paths plus several server-pool API handlers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: pass owned pool to get_email_from_permissioned_as in http trigger handler The generified signature takes impl PgExecutor; the http trigger handler passed &db where db is already &DB, yielding &&Pool which does not impl PgExecutor (only surfaced under the full feature set in CI). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: keep RLS-exposed reads on the non-RLS pool and isolate flow-eval reads in a savepoint Addresses review of the dual-connection sweep: - worker_flow: wrap the stop_after_all_iters_if reads in a SAVEPOINT. The caller swallows the error and keeps using tx, so a DB read failure must not leave the outer transaction aborted (it would fail the later commit). Matches the previous pool-read semantics. - Revert reads that were moved onto an RLS (user_db) transaction back to the non-RLS pool, since RLS row-visibility/role context can change results: push_scheduled_job (email/tag/settings lookups; reachable with a user_db tx from api-schedule/api-flows), push_inner native-retry dedicated_worker routing (RLS isolation variants), resources.rs app-namespace folder auto-create (non-admins must not be blocked), and the script archive/delete UPDATEs. Non-RLS db.begin() reuse and move-before-begin are kept. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test: failpoint proving the stop_after_all_iters_if savepoint isolates an aborted read Adds a worker-crate failpoints feature and a data-driven hook: when the stop_after_all_iters_if expr is the magic sentinel, the in-evaluation read runs SELECT 1/0 to abort its (savepoint) transaction. The test asserts the flow still completes (iteration marked failed) — which only holds if the savepoint keeps the outer status-update transaction committable. Without the savepoint the abort would poison the outer tx and the job would never complete. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
ba768fee88 |
feat(api): add structured endpoint for flow logs (#9797)
Add `GET /w/{workspace}/jobs_u/get_flow_all_logs_structured/{id}` as a
JSON alternative to `get_flow_all_logs`. It returns the same flow log
tree as an array of per-job entries (job_id, label, kind, step path,
depth, parent module type, sibling index/count, and resolved logs)
instead of a single delimited text blob, so callers can render or
process logs per-step without parsing the `=== ... ===` markers.
The shared auth, recursive-CTE query, and label-building logic is
extracted into `collect_flow_log_entries`; the existing text endpoint
now formats those entries and produces byte-identical output.
Fixes WIN-2102
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
5549bdc67a |
fix(debounce): never supersede a running debounce survivor (#9780)
* fix(debounce): never supersede a running debounce survivor
Companion to the windmill-ee-private change in upsert_debounce_key.
With debounce_args_to_accumulate + a concurrent_limit, a message arriving
while its debounce survivor is already running was marked completed/skipped
("Debounced Running by ...") and the running survivor deleted from the
queue, silently dropping accumulated elements. A slow step + concurrent
limit keeps the survivor running for a long window, so any arrival during
it was lost. The fix leaves a running survivor untouched and starts a fresh
debounce window for the late arrival.
Adds regression coverage in windmill-queue/tests/debounce_test.rs (push,
flow post-preprocessing, no-accumulation, committed-running, and
max-count-window cases) and refreshes the SQLx cache for the changed
upsert_debounce_key queries.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(debounce): add missing SQLx cache for test-only running-flag query
The cargo_test CI job compiles the test target with SQLX_OFFLINE=true; the
new regression tests use `UPDATE v2_job_queue SET running = true ...` which
was not in the offline cache (the library-only `cargo sqlx prepare` skipped
test targets). check_oss/check_ee passed because they don't build tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(debounce): harden running-survivor guard against concurrent arrivals
Companion to windmill-ee-private: switch the running-state check to a
correlated EXISTS on the post-conflict-lock holder so two late arrivals
racing after a survivor started running can't both spawn independent
windows (the row lock serializes them; the second debounces into the
first's fresh window).
Adds a concurrent regression test
(test_debounce_concurrent_arrivals_after_running_survivor) asserting
exactly one late arrival survives and the other is debounced, and refreshes
the SQLx cache for the updated upsert_debounce_key queries.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(debounce): serialize upsert per key (simpler, race-free)
Companion to windmill-ee-private: the running-survivor guard and batch
chaining are now protected by a per-key advisory lock instead of
snapshot-sensitive single-statement SQL. This closes a concurrent-arrival
data-loss race where a debounced late arrival's args could be dropped
because the batch lookup couldn't see the predecessor's just-committed
batch row.
Extends test_debounce_concurrent_arrivals_after_running_survivor to pull the
survivor and assert its accumulation includes BOTH racing late arrivals
(shared batch), and refreshes the SQLx cache for the rewritten queries.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(debounce): atomic upsert robust to concurrent pull-time key deletion
Companion to windmill-ee-private: keep upsert_debounce_key a single atomic
INSERT ... ON CONFLICT DO UPDATE so a chaining push cannot fail when the
worker pull path concurrently deletes the holder's debounce_key (the prior
read+UPDATE split could hit "no row updated"). Adds
test_debounce_push_races_key_deletion_by_pull (races a chaining push against
the key deletion 50x, asserts the push never errors) and refreshes the SQLx
cache.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(debounce): claim-based exactly-once batch consumption
Eliminates the rare duplicate/loss when two survivors land on one debounce
batch (a narrow push/pull race), without locking the worker pull hot path.
- migration: v2_job_debounce_batch gains consumed_at + consumed_by.
- pull side (maybe_apply_debouncing): instead of deleting the batch on consume,
a survivor atomically claims its own row + any unclaimed siblings (stamping
consumed_by = itself) and accumulates exactly the rows it claimed. A second
survivor of the same batch finds its row already consumed by another job and
runs empty (no duplicate); a re-pulled survivor recognizes its own prior claim
and keeps its accumulated args; a never-batched job (CE/legacy) keeps its own
args. Non-accumulate debounce paths still hard-delete their batch rows.
- complete_debounced_job (EE companion) never completes a running predecessor,
so its in-flight run is not killed (no loss); the claim then prevents the
duplicate the guard would otherwise allow.
- monitor: GC sweep deletes consumed batch rows past a 1h grace.
Together with the running-survivor guard this makes debounce accumulation
exactly-once. Adds tests: batch_consumed_exactly_once, repull_keeps_accumulated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(debounce): exhaustive edge cases + tighten consumed-batch GC grace
Tighten the consumed debounce-batch GC grace 1h -> 10min: per-op cost of the
claim is unchanged (an indexed mark is as cheap as the old delete), so the only
cost of retaining consumed rows is table growth, which a shorter grace bounds
under high-throughput debounce (a survivor that could still reference a row is
pulled long before 10min; GC is not correctness-critical since a re-pull whose
row was swept falls back to its persisted args).
Adds edge-case tests: never-batched keeps own args (CE fallback), concurrent
claim partitions a batch disjointly (exactly-once under real concurrency),
three survivors -> first takes all / rest run empty, non-accumulate debounce
hard-deletes its batch rows (no leak), and the GC sweep deletes only
past-grace consumed rows.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(debounce): port the #9781 regression case, flow-node guard, full-path bench
- Port the regression from #9781
(test_post_preprocessing_debounce_into_running_survivor_loses_message):
post-preprocessing survivor accumulates + runs, a later same-key message must
start a new batch (survive) not be folded into the running survivor. Exercises
the full EE path via jobs_ee::maybe_debounce_post_preprocessing.
- Add the third EE entry point's guard:
test_flow_node_debounce_running_survivor_not_superseded (maybe_debounce_flow_node).
- Add an #[ignore] full-source throughput bench (bench_debounce_full_path) driving
the real maybe_debounce + maybe_apply_debouncing end-to-end.
All debounce tests exercise the real jobs_ee implementation (run with
--features private,enterprise); none stub it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(debounce): scalar-arg accumulation + GC-then-repull no-loss
Close two accumulation edge gaps (both run on --features private,enterprise,
exercising the real jobs_ee path):
- accumulate bare-scalar values (the T | T[] union fallback): each scalar is
wrapped and accumulated into the survivor's list.
- GC reclaiming a survivor's consumed batch row before a re-pull must not lose
data: the re-pull finds no row and keeps its already-persisted accumulated
args (had_row=false fallback), rather than running empty.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(debounce): real-worker end-to-end accumulation test
Drives the full real path on --features enterprise,deno_core,private: push 3
same-key debounced flow jobs (real push() -> maybe_debounce collapses the
batch), a real worker pulls the survivor (real pull() -> maybe_apply_debouncing
claim+accumulate) and executes the deno flow, then asserts the executed result
is the full accumulated set [1,2,3] and the two superseded messages are skipped.
Complements the in-process unit tests with a genuine worker-execution run.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(debounce): atomic claim+persist, GC only non-queued rows; reword comment
Address review findings:
- [P1] Claim and accumulated-args persist are now in one transaction. Before,
a crash between stamping batch rows consumed_by=self and the `UPDATE v2_job
SET args` could let a zombie re-pull see its own prior claim and keep only its
own args (dropping the siblings it had claimed). Wrapping claim + accumulate +
persist in a tx makes them commit together or roll back together (re-pull then
re-claims cleanly).
- [P1] GC of consumed batch rows now also requires the job to no longer be in
v2_job_queue. A consumed sibling can stay queued well past any time grace under
a concurrency limit / backlog; reclaiming its marker by age alone let its
eventual pull treat it as never-batched and re-run its item (a duplicate).
Keeping the row until the job leaves the queue preserves the "already consumed"
signal. Test extended with a still-queued consumed row that must survive GC.
- [P2] Drop "Customer" attribution from a test doc comment (AGENTS.md).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(debounce): emit accumulation log after committing the claim transaction
append_logs opened a second pool connection while the claim transaction (and its
batch row locks) were still held; under concurrent debounced pulls that risks
pool-exhaustion stalls/timeouts. Defer the log line until after tx.commit().
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 6aabd7c5ce53b9153be05c3e7bc9a76eadb1a48a
This commit updates the EE repository reference after PR #631 was merged in windmill-ee-private.
Previous ee-repo-ref: 30d740e619fad219108ec4b4c6a9d67c1ab42d46
New ee-repo-ref: 6aabd7c5ce53b9153be05c3e7bc9a76eadb1a48a
Automated by sync-ee-ref workflow.
* fix(debounce): claim whole batch in one UPDATE (no deadlock); assert test setup
Both Codex (P1) and Claude (P2) flagged a deadlock: the claim used two writable
CTEs (claim_self then claim_rest), locking the self row before siblings, so two
survivors of the same batch pulled concurrently acquired row locks in opposite
order and PostgreSQL aborted one with deadlock_detected (a transient pull error
on exactly the two-survivors race this path handles).
Replace with a single `UPDATE ... WHERE debounce_batch = (...) AND consumed_at IS
NULL RETURNING id` that claims the whole batch: both transactions lock rows in
the same scan order, so one simply waits and re-evaluates under EvalPlanQual.
A `claimed_self` flag (EXISTS id = self in the claimed set) plus the `mine`
snapshot still distinguishes fresh-claim / consumed-by-other / own-re-pull.
Also assert add_survivor_to_batch_of actually inserts a row (rows_affected == 1)
so a mis-set-up test can't pass vacuously.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
|
||
|
|
aa098c70c0 |
perf: drop v2_job side-table ON DELETE CASCADE FKs to speed retention deletes (#9786)
* perf: drop v2_job side-table ON DELETE CASCADE FKs to speed retention deletes Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: document delete_jobs auth contract and workspace-scope jobs_export purge Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
12f92e3ab7 |
[ee] feat(backend): native script retry without one-step-flow wrapping (#9688)
* feat(backend): native script retry without one-step-flow wrapping Schedules and data pipelines that retry a single script previously wrapped it in a one-step flow (JobKind::SingleStepFlow), creating extra job rows, a v2_job_status row, and UI projection complexity. This adds native retry on a plain JobKind::Script job. - RetrySettings: flatten Retry into a deduped retry_settings table, carried via the existing runnable_settings_handle (lazy, off the hot path). - push() materializes a bare-script-with-retry SingleStepFlow into a native Script job (gated on min-version + no handlers/retry_if). - add_completed_job re-pushes the next attempt on failure with backoff, tracking the attempt counter in v2_job_queue.extras and the chain via parent_job; schedule completion handlers fire only on the terminal attempt. - frontend: ScriptRetryChain shows the attempt chain on the run page. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(backend): native retry_if eval + per-occurrence schedule handlers Extends native script retry to the two cases that previously stayed on the one-step-flow path: - retry_if: evaluated natively on the failure path via a feature-gated windmill-jseval dep (quickjs) over the failure result + flow_input; push materializes such policies natively only when quickjs is available. - on_failure_times / on_recovery: apply_schedule_handlers now resolves each past scheduled occurrence's terminal status across its native-retry chain (root OR any parent_job=root child succeeded) and excludes the current occurrence, so the counting is per-occurrence rather than per-attempt. All scheduled-script retries now go native (schedule.rs gate removed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(backend): always materialize retry_if natively; unsupported without quickjs retry_if is evaluated by the worker (which always has quickjs), not the pusher, so gating materialization on the pusher's feature was wrong. The flow path was never a real fallback either — the flow runtime needs quickjs to evaluate retry_if too. retry_if now always goes native; on a worker without quickjs it is unsupported and fails closed (no retry). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(backend): un-park asset-cascade (pipeline) retry Native retry resolves the blocker that parked pipeline retry: a retried subscriber is now a Script job (not a one-step flow / flow step), so it stays eligible for asset dispatch and can trigger its own downstream on recovery. - scripts.rs: persist // retry <count> [<delay>] to script_trigger on asset edges (was dropped with a TODO warning). - asset_dispatch.rs: is_eligible_kind keys off flow_step_id, not parent_job, so native-retry attempts dispatch on success while flow steps stay excluded. - tests: retry-bearing subscriber now dispatches as a native Script carrying the policy in runnable_settings_handle; native-retry attempt is eligible. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(backend): cap native retry interval, lazy result serialization, idempotent retry push Hardening from a self-review of the native retry path: - Cap the backoff at MAX_RETRY_INTERVAL to match the flow-runtime path (evaluate_retry); the exponential formula could otherwise schedule up to ~18h vs the flow path's 6h. - Serialize the failure result lazily (only when a retry_if policy needs it), so the common failure no longer pays the serialization on the failure path. - Push each retry with a deterministic id per (root, attempt). If a worker dies between enqueueing the retry and finalizing the current attempt, the reaper re-handles the attempt and lands here again — push rejects the duplicate id, so the retry is enqueued exactly once (no double-retry). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(backend): defer schedule handlers idempotently on retry-push replay (review P1) Address local-review findings: - P1: retry_pending was derived from the retry push *result*, so on a worker crash + reaper replay the duplicate-id push returned Err → retry_pending flipped to false → apply_schedule_handlers fired for the non-terminal attempt (and the terminal attempt later fired them again). Pre-check whether the deterministic retry id already exists and report it as pending without re-pushing, so the handler-deferral invariant is crash-idempotent too. - P2: refresh the stale 'wrap the script in a one-step flow' comment in the asset-cascade retry push — it now materializes a native Script. - Add RetrySettings <-> Retry round-trip unit tests (clamping edges). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(backend): native retry chain + per-occurrence status sqlx tests Close the two integration-test gaps flagged in local review: - chains_attempts_and_is_idempotent: drives maybe_enqueue_native_script_retry through attempt0 -> retry1 -> retry2 -> exhausted (counter, backoff, max-attempts) and asserts crash-replay idempotency (the P1 fix: a replayed completion reports pending without double-enqueueing). - per_occurrence_status_counts_recovered_as_success: pins the exact per-occurrence terminal-status query from jobs_ee::apply_schedule_handlers — a retried-but- recovered occurrence counts as success, retries (parent_job set) are excluded from occurrence counting, and the current occurrence is excluded. - canceled_job_does_not_retry: cancellation wins over a pending retry. Runtime sqlx API (no .sqlx cache entry needed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(frontend): exclude schedule handlers from the retry-attempt chain The retry chain listed all script children of the root by parent_job, but schedule completion handlers (on_failure/on_recovery/on_success) are also script children — when the occurrence has no retries, the handler's parent is the root itself, so a successful, never-retried job rendered a bogus 'Retries (1)' badge pointing at the handler. Filter children to re-runs of the same script (matching script_hash); real retries keep the root's hash, handlers run a different script. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(frontend): surface schedule handlers on the run page Extend the run-page chain component with schedule completion handlers: - A 'Handlers' row on a scheduled job links to the on_failure/on_recovery/ on_success runs that fired for that occurrence (found as children of the terminal attempt, identified by their synthetic created_by). - A handler's own run page now shows a 'Failure/Recovery/Success handler' label with a link back to the run it handled and its schedule. on_recovery and on_success share created_by, disambiguated by the recovery-only error_started_at arg. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(backend): restore folder_default_permissioned_as sqlx caches dropped by prepare An earlier `cargo sqlx prepare` on this branch ran before #8801's folder_default_permissioned_as test merged in, so it pruned the 3 query caches that test needs; cargo_test then failed under SQLX_OFFLINE. Restore them from main. * fix(backend): only cascade assets from native retry attempts, not handlers (review P1) is_eligible_kind keyed dispatch on flow_step_id alone, so every parented Script child became asset-eligible — including schedule/error/recovery handlers (Script jobs with parent_job set and no flow_step_id). A handler that declares assets would then trigger a cascade the old parent_job IS NULL guard prevented. Gate parented jobs on being a genuine retry attempt: a re-run of the SAME runnable as its chain parent (handlers run a different script). Runtime query, no sqlx cache. * fix(backend): cache the private-gated retry_setting asset-dispatch test query The same prepare-without-private that dropped the folder_default caches also pruned the cache for the retry_setting_dispatches_subscriber_as_native_script test query (asset_trigger_dispatch.rs:721). Regenerated with --features private. * fix(backend): exclude handler children from per-occurrence recovery (review) A scheduled occurrence's on_failure/on_success handler runs as a successful child (parent_job = occurrence), and the per-occurrence success EXISTS counted ANY successful child — so a failed occurrence whose error handler succeeded was marked 'recovered', breaking on_recovery (test_script/flow_schedule_handlers in the merge) and on_failure_times counting. EE query now scopes the EXISTS to same-runnable children (only native retry attempts); regenerate sqlx cache + bump ee-repo-ref. native_retry_test gains a handler-child regression case. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(backend): scheduled-script retry is a native Script, not SingleStepFlow test_push_script_with_retry / test_try_schedule_with_retry (from main) asserted the old SingleStepFlow wrapping for scheduled-script retry; this PR makes it a native Script. Update both to assert kind='script' and that the retry policy is carried via runnable_settings_handle. * fix(backend): preserve dedicated_worker on native retry + saturate count casts (cubic) Address cubic CI review: - P1: the SingleStepFlow->native Script materialization dropped dedicated_worker, so a dedicated-worker scheduled script lost its dedicated pool on retry. Resolve it from the script row in push so the materialized Script keeps the dedicated tag. - P2: saturate the u32->i32 retry-attempt narrowings (RetrySettings::from) and the u32->i16 // retry count narrowing (scripts.rs) instead of wrapping. * fix(backend): use a retry-specific signal, not runnable equality (codex review) Address Codex CI review: - P1: is_native_retry_attempt treated any same-runnable parented Script child as a retry. WAC v2 inline children have that exact shape, so an inline child of an asset producer would cascade. Use a retry-specific signal instead: the job carries a retry_settings policy (always re-inserted by maybe_enqueue) and has no flow_innermost_root_job. Apply the same flow_innermost guard to the EE per-occurrence EXISTS (WAC inline children must not count as a recovery). - P1: the deterministic retry-id pre-check raced with push; a concurrent duplicate now resolves as 'retry pending' (re-check on the duplicate-id error) instead of flipping retry_pending to false and firing handlers early. - Tests: native_retry + asset_trigger_dispatch gain WAC-inline-child cases. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(backend): explicit native_retry_attempt marker, drop heuristics Replace the per-site "is this a retry?" inference (parent_job + runnable match + flow_innermost / retry_settings) with one explicit marker: a sparse native_retry_attempt(job_id, attempt) table, written in maybe_enqueue. The marker also carries the attempt counter (previously in v2_job_queue.extras), so it's the single source of truth. - asset_dispatch: is_native_retry_attempt is now one indexed EXISTS on the marker. - EE per-occurrence query: joins the marker instead of guessing by runnable/flow_innermost. - maybe_enqueue: reads/writes the marker (persistent) instead of queue extras. - Lifecycle: swept with the job in retention (log_cleanup), no FK to keep bulk delete cheap. - Eliminates handler / WAC-inline-child misclassification by construction. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(backend): sweep native_retry_attempt markers in the periodic retention path too (codex) The marker has no FK and relies on retention cleanup; log_cleanup.rs swept it but the periodic monitor.rs path deleted v2_job rows without it, orphaning markers. Add the same WHERE job_id = ANY(...) sweep there. * fix(backend): widen native_retry_attempt.attempt to integer (cubic) The smallint column was cast to/from u32 and could wrap a retry chain longer than i16::MAX into premature exhaustion. Use integer, matching the retry policy's i32 attempt count, so no narrowing occurs on the maybe_enqueue read/write path. * feat(frontend): mark retries via is_retry on listJobs; drop SAVEPOINT - Expose an is_retry flag on jobs (UnifiedJob/CompletedJob/QueuedJob + openapi), computed from the native_retry_attempt marker. The run-page chain now filters retry attempts by is_retry instead of the script_hash heuristic, so WAC v2 inline children (same script, parent_job) no longer render as retries (codex). - Revert the marker-cleanup SAVEPOINT (an unused pattern in this codebase): keep the plain catch-and-continue matching the other side-table deletes; the table is created by a startup migration so it always exists when cleanup runs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(backend): mark is_retry sqlx(default) so non-list job queries can omit it The single-job GET query maps directly to CompletedJob/QueuedJob via FromRow but does not select is_retry, which errored with "no column found". Only the list endpoint populates the marker; #[sqlx(default)] lets every other query omit the column and default to None. * feat(backend): select is_retry in single-job GET too for consistency The list endpoint already exposes the marker; populate it on the single-job GET (both completed and queued variants) as well so a run loaded directly reflects its retry status. #[sqlx(default)] stays as a safety net for any other query. * feat(backend): reap orphaned native_retry_attempt markers via periodic sweep The marker has no FK to v2_job (to keep the hot bulk retention delete cheap), so direct job deletions (workspace/job delete, schedule clearing) would leave marker rows orphaned. Rather than add explicit cleanup to every v2_job delete site (which must then be remembered for every future path), reap orphans in the periodic delete_expired_items pass: DELETE FROM native_retry_attempt WHERE NOT EXISTS (the job). The table is sparse so the anti-join drives off it and probes v2_job by PK — cheap. Retention still sweeps markers inline (keeps the table small so this stays cheap); a transient orphan is harmless (nothing reads is_retry for a gone job). * fix(frontend): include flow handlers in retry chain handler row (codex) Schedule on_failure/on_recovery/on_success handlers can be flow paths (flow/...), whose handler job is a flow, not a script. The chain fetched children with jobKinds:'script', hiding flow handlers. Drop the kind filter — retry attempts are still selected by is_retry and handlers by created_by, so both kinds surface. * fix(backend): carry concurrency/debouncing settings into native retries maybe_enqueue re-pushed the next attempt with ConcurrencySettings/DebouncingSettings ::default(), dropping the script/pipeline concurrency settings the failed job carried in its runnable_settings_handle. A retry of a concurrency-limited script then inserted no concurrency_key and ran unbounded. Resolve both from the same handle (cached) and pass them in the payload, which push forwards to the materialized retry. Adds a regression test asserting the retry's handle resolves to the concurrency settings. * fix(backend): carry concurrency/debounce into scheduled-retry root + document retry-helper auth (codex) P1a (schedule.rs): the scheduled-retry materialization fetched the script's concurrency/debounce settings but passed ConcurrencySettings/DebouncingSettings ::default() into the SingleStepFlow payload, so the root attempt's handle held only the retry policy and the whole chain ran unbounded. Pass the fetched settings. Regression test asserts the root handle resolves to retry + concurrency. P1b (jobs.rs): document maybe_enqueue_native_script_retry's authorization contract — it is pub only for the integration test; the sole production caller is the worker completion path passing a DB-derived, already-authorized MiniCompletedJob. * docs(backend): attach native-retry auth contract to the function itself (codex) The doc block was merged with eval_retry_if's doc and bound to that function, leaving maybe_enqueue_native_script_retry undocumented. Split them: eval_retry_if keeps its own doc; the native-retry + authorization contract now sits directly above maybe_enqueue_native_script_retry. * docs(backend): regenerate served openapi-deref with is_retry + fix stale comments (codex) - Regenerate openapi-deref.{yaml,json} (served from lib.rs): they were stale since 1.734.0 and lacked is_retry on QueuedJob/CompletedJob, so clients reading the served spec couldn't see the field. Now current at 1.739.0. - schedule.rs: a retry_if gate is evaluated at failure time and fails closed without quickjs (no retry); it does not fall back to a flow path. - windmill-types jobs.rs: is_retry is selected by both the list and single-job GET endpoints (not list-only). * docs(backend): fix remaining stale retry_if/quickjs comments (codex) The retry_if block and the push materialization comments claimed push keeps retry_if on a flow path / the worker always has quickjs. The code always materializes native retry and the no-quickjs eval_retry_if path fails closed — correct the comments to that constraint. * docs(backend): fix stale quickjs-fallback + schedule-handler-restriction comments (codex) - Cargo.toml quickjs feature: without quickjs a retry_if gate cannot be evaluated and the job does not retry (no one-step-flow fallback). - jobs.rs handler-defer comment: apply_schedule_handlers resolves per-occurrence failure/recovery status across the retry chain, so the old 'restricted to schedules whose handlers don't need per-occurrence counting' claim is dropped. --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
043c2c05b7 |
fix: forbid superadmin job tokens from global user and token management (#9715)
* fix: forbid superadmin job tokens from global user and token management Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: extend superadmin job token guard to offboard and export routes Apply forbid_superadmin_job_token to offboard_global_user and export_global_users, the remaining global user-management routes that were gated only by require_super_admin. Offboarding can delete a user along with their tokens, password, invites and instance-group membership, and export returns every user's password_hash, so both must be unreachable by a superadmin job token. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
2879cbb65a |
feat(apps): opt-in sandbox isolation for published & raw apps (alpha) (#9420)
* feat(apps): sandbox published & raw apps with a scoped embed token Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: point ee-repo-ref at embed-token EE commit Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(apps): allow top-navigation from the sandboxed app iframe Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(apps): share app localStorage across apps via the embedder Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(apps): publisher disable-sandbox option with per-version viewer consent Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(sqlx): cache for disable-sandbox queries Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: bump ee-repo-ref to disable-sandbox EE commit Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(apps): always sandbox the served raw-app wrapper + viewer fixes The raw-app wrapper served by get_raw_app_data now always carries `CSP: sandbox`. The publisher "disable sandbox isolation" opt-out is applied entirely on the viewer side, which (after per-version consent) builds its own same-origin blob wrapper — so the backend-served document stays isolated regardless of how it is reached, never via a relaxed real-origin URL. Also: - CORS on the global /apps_u mount so the opaque viewer can load custom-path public apps cross-origin. - Reject runnable-bridge messages unconditionally until the iframe is bound. - Relay the viewer's in-app hash up to the embedder address bar so deep links stay shareable (hash only; embedder keeps its own pathname). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(apps): render public raw apps single-iframe (drop embed token) Public raw apps now render directly on the real origin with a single opaque bundle iframe and the page credential, instead of the opaque viewer + scoped-token indirection. The author bundle stays isolated in its own opaque iframe (CSP-sandboxed); low-code apps, whose code runs in the viewer frame, keep the opaque viewer + scoped token. embed_token now reports raw_app and skips minting a token for raw apps; the access check still gates visibility. Also set disable_sandbox: None in the remaining Policy constructors so the full feature build (all_sqlx_features, enterprise, license) compiles. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore: bump ee-repo-ref to single-iframe raw-app EE commit Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(apps): grandfather existing apps as legacy-unsandboxed + authed-only consent Existing apps are stamped by migration as `legacy_unsandboxed` so they keep running same-origin on upgrade — no breakage and no consent prompt. New apps are sandboxed by default; re-deploying an app clears the flag. The publisher `disable_sandbox` consent prompt is now shown only to authenticated viewers — an anonymous viewer has no session to expose, so the prompt was meaningless friction. embed_token reports `legacy_unsandboxed` and `authed`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore: bump ee-repo-ref to legacy-unsandboxed EE commit Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(apps): deploy-time migration prompt for legacy-unsandboxed apps On the first re-deploy of a grandfathered (legacy-unsandboxed) app, the publisher must explicitly choose: enable sandbox isolation (the flag is cleared → the app becomes sandboxed) or keep running without isolation (→ disable_sandbox, with per-version viewer consent). updatePolicy() no longer carries the legacy flag through a deploy, so the choice is what sticks. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(apps): disable the sandbox-isolation toggle until the app is deployed The Deploy-drawer "Disable sandbox isolation" toggle called setPublishState() — which updates the app by path — even before the app was first deployed, when the path is empty, throwing an error. Guard it with disabled={!savedApp}, matching the adjacent visibility toggle. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(apps): sandbox the in-workspace low-code app viewer in an opaque iframe Extend the opaque-origin iframe isolation to the logged-in /apps/get viewer. /apps/get becomes an embedder that keeps the workspace chrome + Edit button and renders the app inside a cookieless, chrome-less /app_embed viewer route, handed a scoped embed token minted from the member's session. The app frame runs in an opaque origin (no allow-same-origin), so it cannot reach the member's session cookie or window.parent. - apps.rs: get_app_embed_token_for_path (authed, by-path, scope + RLS gated); mint_app_embed_token grants a path-scoped apps:read:{path} so the viewer can load its own app definition and no other - lib.rs: CORS on /apps (bearer-token only, no cookies) for the opaque viewer's by-path reads - new /app_embed/[workspace]/[...path] viewer route (private analog of /public) - PublicAppFrame: viewerUrl prop to point the opaque iframe at the viewer route Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(apps): unify in-workspace app viewers on the shared sandboxed path Route every in-workspace app display (low-code and raw) through the same PublicAppFrame -> PublicApp machinery as the public viewer, so the sandbox / legacy-unsandboxed / disable-sandbox-consent behavior is identical on every page. - new InWorkspaceAppViewer renders both app types via PublicAppFrame; /apps/get and /apps_raw/get become thin wrappers over it - /apps_raw/get previously rendered RawAppPreview directly (always isolated, with no legacy-grandfathering or consent handling); now consistent with the rest - retire the legacy same-origin raw viewer /apps/get_raw/[version] and re-point the apps-list row to /apps_raw/get; remove the dead /apps_raw/[ws]/[version] route - load the raw bundle secret in the shared viewer (getAppByPath doesn't return it) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(apps): address PR review feedback (scope + policy hardening, nits) - require handler-level apps:read on list_apps / list_search_apps so a scoped embed token cannot read app definitions through the list endpoints. The route layer treats apps:run as satisfying read; the handler check (which does not) closes the gap. - treat legacy_unsandboxed as backend-owned: strip any client-provided value in create/update so it can only be set by the grandfather migration, not the API. - document mint_app_embed_token's caller-verifies-access contract. - use Button's declared onClick prop for the consent action (was onclick, which fell into the rest-spread and bypassed the component's click handling). - test: lock that the embed scopes cannot satisfy domain-level apps:read. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(apps): document embed-token endpoints in openapi + fix doc nit Second-round review nits: - add the three app embed-token endpoints (apps/embed_token/p/{path}, apps_u/embed_token/{secret}, and the EE apps_u/embed_token_by_custom_path) plus the EmbedTokenResponse schema to openapi.yaml; note .html on get_data - mint_app_embed_token doc: "Both" -> "All" (it lists three call sites) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(apps): bound embed-token scopes to the caller's own The embed-token mint now enforces ensure_scopes_within_caller, so the minted scope set is always within the calling credential's own scopes (a no-op for regular unscoped sessions). Adds a unit test locking the boundary and documents the contract on mint_app_embed_token. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(apps): raw-app ctx in external embeds + page credential in direct render - RawAppPreview: engage the storage relay only in opaque frames (probe Web Storage instead of just window.parent), so a public raw app embedded in an external iframe hydrates ctx/storage directly; add a relay-timeout fallback so an unresponsive parent can never stall the ctx handshake. - PublicAppFrame: in direct render, expose the page's own bearer credential through the AuthToken context (JWT public URLs), matching the previous route behavior; opaque-viewer mode keeps the embed token. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(apps): sandbox isolation UI polish + COI embed support for raw apps - Deploy drawer: move the sandbox toggle out of "Public URL" into its own "Sandbox isolation" section (the setting applies to every viewing surface, not just the public URL), with positive phrasing, visible helper text, and state-aware alerts (warning when disabled, info for pre-isolation apps). Toggling it now toasts its own message instead of the login-mode one. - Extract the deploy-time migration prompt into a shared LegacySandboxMigrationModal built on the common Modal component, and wire it into the raw app editor header too (it previously had no prompt, so re-deploying a pre-isolation raw app silently changed behavior). updateRawAppPolicy now also drops the backend-owned legacy flag, matching the low-code updatePolicy. - Viewer consent prompt: use the common ConfirmationModal and show the app path (new appPath prop) instead of the route pathname, falling back to "this app" when the path isn't known yet. - COI embeds: propagate the wm_coep opt-in to the raw-app wrapper document and have the backend assert COEP require-corp on it when the flag is present — required for the bundle iframe to load when the public app page is embedded inside a cross-origin-isolated page. Previously this only worked in dev because the Vite proxy injects the header; the production response lacked it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(apps): app navigation parity across sandboxed and direct viewers - Navbar component: same-app items relay query + hash to the embedder page (which mirrors them onto the root URL, keeping its own pathname and transport params), app items navigate the top page through a validated wm_embed_navigate relay instead of the cookieless viewer iframe, and external items keep opening a new tab. Selected-item detection now recognizes the /app_embed viewer route and ignores transport params. - Frontend-script `goto` and button `onSuccess: gotoUrl`: same-window navigation goes through a shared appNavigateSameWindow helper that relays to the embedder inside the opaque viewer (same-origin paths SPA-navigate, http(s) URLs do a full load, other schemes rejected) and keeps plain window.location everywhere else. - /apps/get and /apps_raw/get: key the viewer by workspace/path so in-route navigation fully remounts it — previously the URL changed but the app (and in sandbox mode its path-scoped token) did not follow. - wm_embed/wm_embedder_origin added to the reserved query params so they no longer leak into the app's ctx.query. - Raw apps: drop the sandbox attribute entirely for the unsandboxed (grandfathered/consented) blob path, matching the pre-isolation viewer exactly — the attribute added no isolation there and sandboxed popups (e.g. OAuth flows). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(apps): preserve grandfathered policy across updates + in-workspace viewer parity Round of compatibility hardening so pre-existing apps behave exactly as before on every surface: - `legacy_unsandboxed` is now preserved across app updates unless the payload explicitly clears it (`false`, sent by the editor's migration prompt and the sandbox toggle). Unrelated update paths — CLI / git-sync redeploys, publish-mode toggles, cross-workspace promotion — no longer silently drop the grandfathering. Clients still can never SET the flag. - The embed-token endpoints (secret, path, EE custom-path) read only the sandbox-decision policy fields, leniently, and no longer mint a token for raw / legacy / disable_sandbox renders: the token is only consumed by the sandboxed low-code render, and minting for the others wrote a useless token row per view and could fail the render for scope-restricted callers. - In-workspace viewer parity with the pre-sandbox `/apps/get`: new `inWorkspace` mode on PublicApp (no "Powered by Windmill" badge / user overlay, no HTML-result approval gate, column flex wrapper, `hideRefreshBar` honored again), and the page's query/hash are forwarded into the opaque viewer so `ctx.query` / `ctx.hash` reach the app. - Raw apps: `window.ctx` is always `{ctx, workspace}` again (anonymous viewers of pre-existing bundles rely on `ctx.workspace`), and the runnable bridge's job-id scoping now applies only to sandboxed renders (`gateJobIds`) — an unsandboxed bundle holds the same credential as the bridge, so gating there only broke pre-existing apps polling persisted or runnable-returned job ids. - Document `disable_sandbox` / `legacy_unsandboxed` in the openapi Policy schema; add a unit test for the lenient policy read. - bump ee-repo-ref to the matching EE commit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(apps): keep share-link viewer credentials out of the isolated app context The JWT path segment of authenticated share URLs is an embedder-side credential, consumed only to mint the scoped embed token. Two transport channels still copied it into the isolated frame where app-authored code runs: - the opaque viewer iframe src defaulted to window.location.href — the public and custom-path routes now pass a sanitized viewerUrl (JWT segment stripped, query/hash preserved, captured once so the hash relay does not reload the iframe); - document.referrer on the same-origin iframe navigation carried the full embedder URL — both app iframes now set referrerpolicy="no-referrer" (sandboxed renders only for the raw bundle iframe, keeping exact legacy parity; nothing reads the referrer). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(frontend): drop unused import inherited from main merge `slide` import in AssistantMessage.svelte (from #9539) turns `npm run check` red on this branch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(apps): redirect the removed raw-app viewer path to the unified viewer The old same-origin raw-app viewer route (/apps/get_raw/{version}/{path}) was removed in favor of the sandboxed unified viewer. Re-add a thin client route at the old path that redirects stale bookmarks to /apps_raw/get/{path}, preserving query + hash (the pinned version is dropped — the unified viewer shows latest). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(apps): narrow embed-token scopes and base consent on browser session - Embed token: resource access is metadata-only (list/type/exists) via a `resources:run` marker — resource values (get/get_value/get_value_interpolated/ list_search) are no longer reachable. Job reads are by-id only: an `app_embed` sentinel blocks the workspace-wide job enumeration/export routes (jobs/list, list_filtered_uuids, queue/list, completed/list, queue/export) while by-id result polling keeps working. - disable_sandbox consent now gates on whether the browser holds any Windmill session (cookie-only whoami) rather than workspace-scoped auth, so a viewer logged into a different workspace is still prompted before a same-origin render. - db-explorer: resolve the MySQL database name server-side (the metadata query already falls back to DATABASE()) instead of reading the resource value client-side; getTablesByResource derives the default db from the schema. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(apps): trim embed-scope and consent comments Reduce duplication — state the resource/job route exclusions and the workspace-session-vs-cookie rationale once at their source and reference them elsewhere; drop contrast/justification phrasing. No behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(apps): make app sandbox isolation opt-in (alpha) Replace the disable_sandbox + legacy_unsandboxed policy pair and the per-version viewer consent with a single positive `sandbox` opt-in flag. Apps are unsandboxed by default (same-origin, full session — the pre-isolation behavior), so existing apps are unchanged and no migration is needed. Publishers opt an app into isolation from the deploy drawer, flagged alpha. - Policy.sandbox: Option<bool>; EmbedTokenResponse -> {token, expiration, raw_app, sandbox}; mint an embed token only for sandboxed low-code apps. - Drop the legacy-unsandboxed migration and the deploy-time migration prompt; remove the consent modal and the browser-session probe. - Deploy drawer: a single "Sandbox isolation" toggle (alpha), off by default, shared by the low-code and raw editors. - Bump ee-repo-ref to the companion EE commit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(apps): confine embed token to its intended user/folder/job routes The embed token's broad read scopes spanned whole domains while the matching routers are CORS-enabled for the opaque app iframe: - users:read / folders:read were domain-wide, so the token could reach users/list, users/list_usage, users/username_to_email/*, folders/list, etc. Restrict to an app_embed-sentinel allowlist: only users/whoami and folders/listnames; deny the rest of those domains. - jobs:read allowed jobs/completed/export, missed by the job denylist. Add it alongside jobs/queue/export. Extend the embed-scope allow/deny test matrix to cover all of these. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(apps): align sandbox comments with the opt-in model The consent prompt, deploy-time migration, and legacy-unsandboxed grandfathering were removed when sandbox isolation became an opt-in policy flag; update the comments that still described them so they match the two-state (default-unsandboxed / opt-in-sandboxed) reality. Comments only, no behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(apps): confine embed-token job reads to runs the app launched App component jobs are stamped `created_by = the viewer`, so an embed token reads its own runs via the launched-by-viewer fast path. The token then also inherited the viewer's broader job access (share links, folder ACLs, admin RLS), letting user-authored app JS reuse it to read unrelated jobs by id. Stop embed tokens at the fast path: only jobs the viewer launched, never those merely visible to them. Return NotFound so the untrusted app can't probe existence. Regression test: an embed token reads its own launched job but is denied the foreign job (result/logs/getupdate) an admin viewer's normal token can read. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(apps): allowlist embed-token apps/jobs routes + scope run to the app The embed token's apps:run/jobs:read reached more than a running app needs. Replace the job denylist with strict per-domain allowlists on the app_embed sentinel: - Apps: only the app's own definition (apps/get/p/<path>) and the public app-serving endpoints (apps_u/*). Denies workspace app inventory (exists, custom_path_exists, list, list_paths*). - Jobs: only the by-id poll routes the frontend JobLoader uses. Denies job counts and the job_signature/resume_urls capability-minting routes (the by-id reads remain confined to the app's own runs). Drop unqualified apps:run from APP_EMBED_SCOPES; mint apps:run:<path> instead and authorize apps:run:<requested path> first in execute_component, so the token can only run its own app's components, not another app's. Extend the embed-scope route matrix and add a path-scoped run unit test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(apps): clarify the sandbox toggle vs the on-behalf-of model The deploy-drawer sandbox copy leaned on "session" in a way that collided with the on-behalf-of permissioning right above it. Reword it to say the toggle governs what the app's browser-side code can reach in the viewer's browser — distinct from who its runnables execute as — and rename the label to "Isolate the app from the viewer's browser session". Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(apps): path-scope embed-token S3 download to its own app The apps_u/* allowlist also admitted apps_u/download_s3_file/<path>, whose handler authorized any authenticated caller — so an embed token minted for app A could download app B's S3 files via B's on-behalf policy. Add the same path-scoped guard execute_component uses: download_s3_file_from_app now checks apps:read:<path> first, confining the token to its own app. Other path-taking apps_u routes are already covered (writes lack apps:write; embed_token/p path-checks; public_resource is type-constrained). Extend the path-scoping unit test to cover apps:read (download) alongside apps:run (execute). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(apps): path-scope public-app-by-secret read to the embed token's app The apps_u/* allowlist admitted apps_u/public_app/<secret>, whose handler only checked the viewer's read access — so an embed token minted for app A could read app B's definition by secret (confused deputy via the viewer's identity). get_public_app_by_secret now binds a scoped caller to the resolved app with check_scopes(apps:read:<path>), confining it to its own app; unscoped sessions and anonymous access are unchanged. get_raw_app_data needs no binding (pure secret capability, no caller identity). Document the full set of app-resolving handlers the path-scoped read covers. Bump ee-repo-ref for the companion custom-path fix. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(apps): preserve pre-sandbox behavior for db-explorer, edit link, jwt Three behavior-parity fixes for non-sandboxed (existing) apps that the sandbox-isolation refactor changed incidentally: - DB-explorer MySQL table picker: when the connection can see multiple non-system schemas, label the default db's tables unprefixed again. The resource-value read was removed globally, so identify the default db from the introspection script's `DATABASE() AS default_db_name` (carried on SQLSchema.defaultDb) instead of guessing "the single schema key". Equivalent to the prior resource.database match; editor-only (table picker). - In-workspace Edit button: restore `?nodraft=true` on both /apps/get and /apps_raw/get, so opening the editor from the viewer loads the deployed version, not a draft. - Custom-path (/a) viewer: restore the "could not authenticate user with jwt token" toast when a path JWT fails to resolve a user, instead of silently falling through. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(apps): confine embed-token S3 downloads to the app's own keys/outputs download_s3_file_from_app authorized any authenticated caller for any S3 key (opt_authed.is_some() bypass). A sandboxed app's embed token carries the viewer's identity, so app-authored JS could fetch arbitrary S3 keys readable by the on-behalf identity, beyond the app's own declared keys or outputs. Route app embed tokens through the same allowlist as anonymous viewers — the app's declared allowed_s3_keys, or files produced by this app's own component runs — instead of the authed bypass. The produced-files check is parameterized by created_by (the embed viewer for a token, else anonymous) so a sandboxed app's own S3 outputs still render while arbitrary keys are denied. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(apps): let embed tokens cancel their own jobs; gate cancel to launcher A sandboxed low-code app supersedes an in-flight component run on re-run by canceling it, but the embed token only had jobs:read, so cancellation silently failed and prior jobs ran to completion. - Permit the by-id jobs_u/queue/cancel POST for app_embed tokens at the route layer (the only write reachable through the existing by-id allowlist). - Gate cancel_job_api: an app_embed token may cancel ONLY jobs it launched (created_by == viewer). cancel_job_api had no other per-job ownership check, so this also confines the token instead of letting it cancel any job by id. - /app_embed now sets workspaceStore so cancellation targets the right workspace instead of an empty/stale one in the cookieless iframe. Add a shared has_app_embed_sentinel helper; cover cancel in the route matrix and the jobs_read_auth integration test (own job cancelable, foreign denied). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(apps): drop get_root_job_id from the embed-token job allowlist Audit of the embed token's reachable job routes: get_root_job (jobs_u/ get_root_job_id) has no access check in its handler at all — it returns any job's root-job id by id — and the app runtime never calls it. Remove it from the by-id allowlist so the embed token can't probe a foreign job's flow lineage; add a denied-route assertion. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(apps): scope sandboxed-app localStorage per app Sandboxed apps shared one localStorage store (one key on the real origin), so an app could read or clobber another app's keys — and, with job ids stashed there, reuse its embed token to read another app's job. Scope the backing store per app. The embed-token endpoints now return the resolved app_path (EmbedTokenResponse; not a new disclosure — the viewer already receives the path when it loads the app). PublicAppFrame (low-code) and RawAppPreview (raw) key their backing store by it: wm_apps_localstorage:<app_path>. Same app shares one store across its public and in-workspace surfaces; different apps are isolated. Unsandboxed apps are unaffected (real same-origin localStorage, as before). Bump ee-repo-ref for the companion custom-path change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(apps): scope embed access checks to embed tokens + key app storage by workspace - Apply the path-scoped read/run checks on the public-by-secret read and the component run path only when the caller is an app embed token, so other caller types keep their prior access. - Key the sandboxed app's backing client storage by workspace + path instead of path alone, and return the resolved workspace from the embed-token endpoints so the custom-path viewer can derive it. - Show a clear message instead of an indefinite loader when the viewer route is opened outside its embedder. Bumps ee-repo-ref to 5b8476b. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(apps): mint embed tokens only from the trusted embedder caller An app embed token must not reach the embed-token mint endpoints; refresh minting stays with the embedder session/JWT. Enforced at the scope route layer and at the mint chokepoint, with a route-matrix regression test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(apps): support S3 upload and frontend-script S3 download in sandboxed apps Sandboxed apps run with a scoped embed token (no cookie). Let the app's S3 file-input upload and the frontend-script download({s3}) helper work in that context: upload is reachable with apps:run and re-checked per-app at the handler; the script download routes through the app-scoped apps_u endpoint with the embed token instead of the cookie-authed job_helpers path. Default (unsandboxed) apps are unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore: update ee-repo-ref to b0cb761bf9852974e571b2978032d310cc998517 This commit updates the EE repository reference after PR #600 was merged in windmill-ee-private. Previous ee-repo-ref: e673c714a4618fdb72353a475f49c748e6016642 New ee-repo-ref: b0cb761bf9852974e571b2978032d310cc998517 Automated by sync-ee-ref workflow. --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com> Co-authored-by: Ruben Fiszel <ruben@windmill.dev> |
||
|
|
6aa9e515f7 |
de-flake asset-dispatch debounce test by reading settings from DB (#9687)
The debounce assertion resolved the dispatched job's debounce window through `prefetch_cached_from_handle`, which goes through the process-global runnable-settings cache (shared by every test running concurrently in the binary) and its tempdir-backed file I/O. Read the persisted rows directly from the test's isolated DB instead, removing that cross-test coupling and extra I/O from the assertion path. Still validates the full wiring (handle -> runnable_settings -> debouncing_settings). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |