diff --git a/.agents/skills/adding-a-trigger/SKILL.md b/.agents/skills/adding-a-trigger/SKILL.md
new file mode 100644
index 0000000000..7d8643b862
--- /dev/null
+++ b/.agents/skills/adding-a-trigger/SKILL.md
@@ -0,0 +1,267 @@
+---
+name: adding-a-trigger
+description: Checklist for adding a new TriggerCrud-based trigger type to Windmill (Azure, GCP, Kafka, etc.). Use when wiring a new trigger kind across backend, frontend, CLI, and capture infrastructure.
+---
+
+# Skill: Adding a New Trigger Type
+
+Use this skill when adding a trigger kind that implements `TriggerCrud` (Kafka, GCP, Azure, MQTT, SQS, NATS, Postgres, Email…). For native triggers (Nextcloud, Google Drive — things wired through `windmill-native-triggers`), use the `native-trigger` skill instead.
+
+The goal of this doc is to enumerate every file that needs to change. Missing any one of them leads to silent regressions: sync drops the trigger, capture button does nothing, workspace forks lose it, sidebar counters undercount. Follow the checklist top-to-bottom — each section is independent enough to be validated on its own.
+
+Throughout this doc, substitute `{kind}` for the new trigger kind (`azure`, `kafka`, …), `{Kind}` for PascalCase (`Azure`, `Kafka`), `{KIND}` for SCREAMING (`AZURE`, `KAFKA`).
+
+## Reference implementations
+
+- **GCP** — closest analogue to Azure. Has push + pull, OIDC auth, ARM-like resource paths, capture handler. Grep for `gcp_trigger` / `GcpTrigger`.
+- **Kafka** — simpler (pull-only, streaming). Good for trivial integrations.
+- **Azure** — most recently added (2026). Shared-secret push auth, Event Grid namespaces + basic topics, ARM resource discovery, Namespace-pull data-plane. Grep for `azure_trigger` / `AzureTrigger`.
+
+## 1. Database migration
+
+Create a migration: `cargo sqlx migrate add -r add_{kind}_trigger` from `backend/`. Never write timestamps manually.
+
+The `up.sql` usually defines:
+- An optional enum type (e.g. `AZURE_MODE`) if the trigger has sub-kinds
+- The `{kind}_trigger` table with at minimum these columns (mirrored from kafka/gcp):
+ - primary: `(workspace_id, path)`
+ - `script_path`, `is_flow`, `enabled`, `mode`, `permissioned_as`, `edited_by`, `email`
+ - `edited_at`, `error`, `server_id`, `last_server_ping`
+ - `error_handler_path`, `error_handler_args jsonb`, `retry jsonb`
+ - trigger-specific fields
+- Indexes on foreign keys + any frequently-filtered columns
+- Foreign key to `workspace`
+
+Down migration drops the table and any enum types.
+
+## 2. Backend crate (`windmill-trigger-{kind}`)
+
+Create a new crate under `backend/windmill-trigger-{kind}/` with:
+
+- `Cargo.toml`: features `enterprise`, `private` if EE, standard deps
+- `src/lib.rs`: `pub use mod_ee::*;` behind `#[cfg(all(feature = "enterprise", feature = "private"))]`
+- `src/mod_ee.rs`: core types + helpers
+- `src/handler_ee.rs`: `TriggerCrud` impl + route handlers
+- `src/listener_ee.rs`: (only if streaming/pull-based) `Listener` trait impl
+
+Required in `mod_ee.rs`:
+- `{Kind}Config` struct (persisted shape, `FromRow`)
+- `{Kind}ConfigRequest` struct (what API receives — usually similar to Config but with validation fields)
+- `{Kind}Trigger` unit struct (implements the traits)
+- `impl TriggerJobArgs for {Kind}Trigger` — sets `TRIGGER_KIND`, `Payload`, `v1_payload_fn`
+
+Required in `handler_ee.rs`:
+- `#[async_trait] impl TriggerCrud for {Kind}Trigger` with:
+ - `type Trigger = Trigger<{Kind}Config>`
+ - `type TriggerConfigRequest = {Kind}ConfigRequest`
+ - `const ROUTE_PREFIX: &'static str = "/{kind}_triggers";`
+ - `const TABLE_NAME`, `ADDITIONAL_SELECT_FIELDS`
+ - `get_deployed_object`, `validate_config`, `create_trigger`, `update_trigger`, `delete_trigger`, `test_connection`
+ - `additional_routes` (optional — mount extra endpoints for things like ARM resource listing, topic discovery)
+
+Register the crate in `backend/Cargo.toml` as a workspace member and as a dep of `windmill-api` behind the feature flag.
+
+## 3. Wire into `windmill-api` (feature-gated everywhere)
+
+**`backend/windmill-api/src/triggers/handler.rs`** — mount the trigger crate:
+```rust
+#[cfg(all(feature = "enterprise", feature = "{kind}_trigger", feature = "private"))]
+{
+ use crate::triggers::{kind}::{Kind}Trigger;
+ router = router.nest({Kind}Trigger::ROUTE_PREFIX, complete_trigger_routes({Kind}Trigger));
+}
+```
+
+**`backend/windmill-api/src/triggers/{kind}/mod.rs`** — re-export the crate:
+```rust
+pub use windmill_trigger_{kind}::*;
+```
+
+**`backend/windmill-api/src/lib.rs`** — if the trigger receives inbound pushes, add a webhook route:
+```rust
+.nest("/{kind}/w/{workspace_id}", {
+ #[cfg(all(feature = "enterprise", feature = "{kind}_trigger", feature = "private"))]
+ { triggers::{kind}::handler_oss::{kind}_push_route_handler() }
+ #[cfg(not(...))]
+ { Router::new() }
+})
+```
+
+## 4. `TriggerKind` enum (`backend/windmill-types/src/triggers.rs`)
+
+Already has slots for most triggers but verify your variant exists:
+- Add `{Kind}` to the `TriggerKind` enum
+- Add match arm in `to_key()`
+- Add match arm in `from_str`
+- Add match arm in `JobTriggerKind` (if jobs need kind tagging)
+
+## 5. OpenAPI (`backend/windmill-api/openapi.yaml`)
+
+This file is huge and the single most-forgotten place. Add:
+
+- `/w/{workspace}/{kind}_triggers/create` + `/update/{path}` + `/delete/{path}` + `/get/{path}` + `/list` + `/exists/{path}` + `/setmode/{path}` + `/test` paths (mirror gcp section)
+- Any `additional_routes` your handler exposes (resource discovery, etc.)
+- Schemas: `{Kind}Trigger`, `{Kind}TriggerData`, `{Kind}Mode` (if enum), `{Kind}DeliveryConfig`, helper request/response types
+- Add `{kind}` to `CaptureTriggerKind` enum
+- Add `{kind}_used: boolean` to the `UsedTriggers` response schema
+
+Regenerate frontend client: `npm run generate-backend-client` from `frontend/`.
+
+## 6. `UsedTriggers` + workspace export
+
+**`backend/windmill-api-workspaces/src/workspaces.rs`** — add `{kind}_used: bool` to the `UsedTriggers` struct and add an `EXISTS(SELECT 1 FROM {kind}_trigger …)` to the `get_used_triggers` query.
+
+**`backend/windmill-api/src/workspaces_export.rs`** — add export block mirroring gcp's (export lists all triggers, serializes them to YAML/JSON). The block re-uses the `trigger_ignore_keys` variable so the new kind automatically participates in fork-export stripping (`mode` field is omitted when the source workspace is a fork — keeps fork→parent merges from flipping the parent's enabled state).
+
+**Fork cloning (`clone_triggers_and_schedules` in workspaces.rs)** — add an `INSERT INTO {kind}_trigger ... SELECT ...` block that copies all rows from the parent workspace, forcing `mode = 'disabled'::TRIGGER_MODE`. Always runs at fork creation; forgetting this means users can't carry `{kind}` triggers into their forks.
+
+## 6.5 Hardcoded trigger-kind arrays (silent-failure hotspots)
+
+Several files keep **hardcoded arrays** of trigger kind strings. Miss one and ACL checks / user offboarding / trash drop your kind:
+
+- **`backend/windmill-api-groups/src/granular_acls.rs`** — `KINDS: [&str; N]`. **Increment N** (the compile error is cryptic otherwise). Controls which kinds accept granular ACL operations.
+- **`backend/windmill-api-users/src/users.rs`** (`extra_perms_tables`) — which tables get `extra_perms` entries cleaned when a user is deleted.
+- **`backend/windmill-api/src/offboarding.rs`** — three separate arrays (enumeration, fork-copy, and delete paths). **All three** need the new kind.
+- **`backend/windmill-api/src/trash.rs`** — `valid_tables` for the trash / restore API.
+- **`backend/windmill-git-sync/src/lib.rs`** — add a test assertion for `DeployedObject::{Kind}Trigger.get_kind() == "{kind}_trigger"` (the `get_kind` match arm itself lives in the enum impl — already required by the Rust compiler).
+- **`backend/windmill-api-auth/src/scopes.rs`** — add the `{Kind}Triggers` variant to `ScopeDomain` enum + `as_str` match + `from_str` match. Required for the OAuth/token system to recognise `{kind}_triggers:read|write` scopes.
+- **`backend/windmill-api/src/token.rs`** (`build_trigger_scope_domains` → `TRIGGER_DOMAINS`) — add `("{kind}_triggers", "{Kind display name}")` so the CreateToken UI's scope selector surfaces the `read` / `write` checkboxes.
+
+**OpenAPI enums** to extend (do NOT forget — generated client will allow it but server rejects as 400):
+- `CaptureTriggerKind` enum
+- Three `kind` enums under `/w/{workspace}/acls/{get,add,remove}/{kind}/{path}` (yes, same list repeated three times)
+
+After editing any of these, run a full `cargo check` with your feature flag + `gcp_trigger` + other core flags — the `KINDS: [&str; N]` length mismatch only surfaces when the crate compiles.
+
+## 7. Capture infrastructure (`backend/windmill-api/src/capture.rs`)
+
+If the trigger supports push delivery, it also needs a capture endpoint so users can test it:
+
+- `{Kind}TriggerConfig` struct (gated by feature flags)
+- `TriggerConfig::{Kind}` variant
+- `set_{kind}_trigger_config` function (creates the subscription/equivalent pointing at the capture URL — use your `manage_{kind}_subscription` helper with `trigger_mode=false`)
+- Both real + no-op versions behind feature gates
+- `TriggerKind::{Kind} => set_{kind}_trigger_config(...)` arm in `set_config`
+- `{kind}_payload` async handler — validates auth (if any), processes payload, calls `insert_capture_payload`
+- Route: `.route("/{kind}/{runnable_kind}/{*path}", post({kind}_payload))` inside `workspaced_unauthed_service` — and expand the surrounding `#[cfg(any(...))]` to include your feature flag
+
+## 8. CLI (`cli/`) — easy to miss, breaks sync silently
+
+Check all of these:
+
+**`cli/src/types.ts`:**
+- Add `"{kind}"` to `TRIGGER_TYPES` array
+- Add `"{kind}_trigger"` to `getTypeStrFromPath` return union
+- Add match case in `getTypeStrFromPath`'s `typeEnding ===` chain
+- Add `pushTrigger("{kind}", ...)` branch in `pushObj`
+
+**`cli/src/commands/trigger/trigger.ts`:**
+- Import `{Kind}Trigger` type
+- Add `{kind}: {Kind}Trigger` to the `Trigger` type map
+- Add `{kind}: wmill.get{Kind}Trigger`, `update{Kind}Trigger`, `create{Kind}Trigger` to each function map
+- Add `{kind}: { ... }` template to `triggerTemplates`
+- Add `list{Kind}Triggers` call + spread in the `list` aggregation
+- Update `--kind` option descriptions to mention the new kind
+
+**`cli/src/commands/sync/sync.ts`:**
+- Add `path.endsWith(".{kind}_trigger" + ext)` in the file-type filter
+- Add `typ == "{kind}_trigger"` in `getTypeOrder`
+- Add `"{kind}_trigger"` to the delete-suffix regex (~line 3092)
+- Add a `case "{kind}_trigger"` in the delete switch
+
+**`cli/src/guidance/skills.ts`** — **DO NOT EDIT DIRECTLY**. It's auto-generated by `system_prompts/generate.py`. Instead:
+- Edit `system_prompts/utils.py` → append `('{Kind}Trigger', '{kind}_trigger')` to the `SCHEMA_MAPPINGS['triggers']` list (this is the master list — the one in `generate.py` is duplicated and `utils.py` wins)
+- Then run `python3 system_prompts/generate.py` — it regenerates `cli/src/guidance/skills.ts` with the schema extracted from `backend/windmill-api/openapi.yaml`
+- Commit the regenerated file
+
+## 9. Frontend — editor + drawer
+
+Under `frontend/src/lib/components/triggers/{kind}/`:
+
+- `{Kind}TriggerPanel.svelte` — the tile shown in the triggers listing
+- `{Kind}TriggerEditor.svelte` — outer drawer wrapper
+- `{Kind}TriggerEditorInner.svelte` — state + business logic; must expose:
+ - `openEdit(path, isFlow, defaultValues?)` method
+ - `isEditor` prop, `onConfigChange` + `onCaptureConfigChange` callbacks
+ - `get{Kind}Config()` + `get{Kind}CaptureConfig()` helpers
+ - `captureConfig = $derived.by(untrack(() => isEditor) ? get{Kind}CaptureConfig : () => ({}))`
+ - `$effect(() => { const args = [captureConfig, isValid] as const; untrack(() => onCaptureConfigChange?.(...args)) })`
+- `{Kind}TriggerEditorConfigSection.svelte` — form fields; use design-system components (`TextInput`, `Select`, `Toggle`, `ToggleButtonGroup`), never raw ` `
+- `{Kind}Capture.svelte` — capture panel; wraps `CaptureSection` with `captureType="{kind}"`
+- `utils.ts` — `requestBody` builders and any trigger-type-specific helpers
+
+## 10. Frontend — global integration
+
+Easy to miss:
+
+- **`frontend/src/lib/components/triggers.ts`** — add `'{kind}'` to the `TriggerKind` union
+- **`frontend/src/lib/components/triggers/CaptureWrapper.svelte`**:
+ - Import `{Kind}Capture`
+ - Add to `isStreamingCapture()` array (streaming = pull-style; push-style is typically `false`)
+ - Add `{:else if captureType === '{kind}'}` branch with the `<{Kind}Capture>` render
+- **`frontend/src/lib/components/sidebar/SidebarContent.svelte`** — import the icon, add the nav entry
+- **`frontend/src/lib/components/sidebar/OperatorMenu.svelte`** — add the operator-mode entry
+- **`frontend/src/routes/(root)/(logged)/+layout.svelte`** — destructure `{kind}_used` from `/get_used_triggers` response, push `'{kind}'` into `usedKinds`
+- **`frontend/src/lib/components/search/GlobalSearchModal.svelte`** — import icon, add "Go to {Kind} ..." entry
+- **`frontend/src/lib/components/offboarding-utils.ts`** — add mappings `{kind}_trigger: '{kind}_triggers'` and `{kind}_trigger: '{kind} trigger'`
+- **`frontend/src/lib/components/icons/{Kind}Icon.svelte`** — single-path SVG, `fill={color ?? 'currentColor'}`, `size` prop default 16 (match existing icons — don't hardcode colors, don't use `width`/`height` props)
+- **`frontend/src/routes/(root)/(logged)/{kind}_triggers/+page.svelte`** — listing page (mirror `gcp_triggers/+page.svelte` for push+pull, `kafka_triggers` for pure streaming)
+- **`frontend/src/lib/components/CompareWorkspaces.svelte`** — workspace fork / compare tool. Needs: service import, editor import, `{kind}Editor` `$state`, `case '{kind}'` in `openTriggerDetails()`, entry in `triggerServices` object (list/delete/normalize), and `<{Kind}TriggerEditor bind:this={{kind}Editor} />` in the template
+
+## 10.5 AI system prompts (`system_prompts/`)
+
+- **`system_prompts/utils.py`** — append `('{Kind}Trigger', '{kind}_trigger')` to `SCHEMA_MAPPINGS['triggers']` (master list used by code generation + CLI skills)
+- **`system_prompts/generate.py`** — also has a duplicated `schema_types` list (~line 903) for the AI `triggers` skill content. Add `('{Kind}Trigger', '{kind}_trigger')` there too
+- **`system_prompts/generate.py`** `schema_names` (~line 1192) — add `'{Kind}Trigger'` (add `'New{Kind}Trigger'` only if the OpenAPI declares one; GCP and Azure don't)
+- Run `python3 system_prompts/generate.py` — this rewrites `cli/src/guidance/skills.ts` and all `auto-generated/` docs. Commit the regenerated files
+
+## 11. Validation
+
+Run all of these before declaring done:
+
+```bash
+# Backend
+cd backend
+cargo check --features enterprise,{kind}_trigger,private # minimal
+cargo check --features enterprise,azure_trigger,private,gcp_trigger,http_trigger,mqtt_trigger,postgres_trigger,sqs_trigger,kafka,nats,smtp,websocket # full
+
+# SQLx offline data (never run `cargo sqlx prepare` directly — use the wrapper)
+./update_sqlx.sh
+
+# Frontend
+cd frontend
+npm run generate-backend-client
+npm run check:fast
+```
+
+Smoke test in the UI: create a trigger, save, check it appears in sidebar + search, delete, re-create via CLI `wmill sync`.
+
+## 12. Common pitfalls
+
+- **Forgetting feature gates in `workspaced_unauthed_service()`** — the surrounding `#[cfg(any(...))]` expression must include your feature flag, not just the inner `#[cfg]` on the route
+- **`.route(path, ...).route(path, ...)` with same path and different methods** — older axum replaced; use `.route(path, post(h1).options(h2))` to chain methods on the same `MethodRouter`
+- **`on:event` directives** — legacy Svelte 4, no-op in runes mode. Use callback props (`onSelected`, `onConfigChange`)
+- **`$bindable(default_value)` on optional props** — banned by project CLAUDE.md. Use `$bindable()` + `$derived(prop ?? default)` instead
+- **CORS layer intercepting OPTIONS** — tower-http CorsLayer short-circuits OPTIONS before reaching your handler. For server-to-server webhook endpoints, drop the CORS layer entirely (CORS is browser-only)
+- **DeliveryAttributeMappings / custom headers for auth** — prefer HMAC or sha256-hashed shared secrets over opaque JWTs when the provider doesn't support signed tokens natively. Store only the hash; regenerate secret on every save
+- **ARM / API resource-listing cascades** — if the trigger's resource type is deep (Azure: subscription → RG → namespace → topic), offer dropdowns in the UI populated from the provider's APIs using the user's credential resource
+- **Clearing stale selections on dependency change** — when a dropdown's underlying data reloads (e.g., user changes SP or edition), clear selections that no longer match the new list
+- **Workspace-scoped tag compatibility** — if the trigger has tags, verify forked workspaces handle them (see commit `0773b5bc85` for a historical fix)
+
+## 13. EE file split
+
+If the trigger is enterprise-only, the code lives in `windmill-ee-private__worktrees/.../windmill-trigger-{kind}/src/*_ee.rs` and is symlinked into the OSS tree. The `windmill-ee-private__worktrees/` directory holds the real files; changes propagate via symlinks. See `docs/enterprise.md` for the workflow.
+
+## 14. Final checklist before PR
+
+- [ ] Migration up/down tested (revert + re-apply)
+- [ ] `./update_sqlx.sh` committed the updated `.sqlx/` offline data
+- [ ] `cargo check` passes with your feature flag + with all trigger features
+- [ ] `npm run check:fast` passes
+- [ ] Trigger visible in sidebar with correct icon weight (not oversized/colored — use `currentColor`)
+- [ ] Create, edit, delete flow all work in the UI
+- [ ] Capture button works (if push-capable)
+- [ ] Trigger appears in `/get_used_triggers` → sidebar pulse
+- [ ] `wmill sync pull` + `wmill sync push` both round-trip the trigger
+- [ ] `wmill trigger list` includes it
+- [ ] OpenAPI schemas are complete (no `null` in generated types)
diff --git a/.agents/skills/commit/SKILL.md b/.agents/skills/commit/SKILL.md
index 3f97552466..114531570e 100644
--- a/.agents/skills/commit/SKILL.md
+++ b/.agents/skills/commit/SKILL.md
@@ -1,5 +1,6 @@
---
name: commit
+user_invocable: true
description: Create a git commit with conventional commit format. MUST use anytime you want to commit changes.
---
diff --git a/.agents/skills/local-review/SKILL.md b/.agents/skills/local-review/SKILL.md
index ad701ac367..316daa0fea 100644
--- a/.agents/skills/local-review/SKILL.md
+++ b/.agents/skills/local-review/SKILL.md
@@ -1,97 +1,98 @@
---
name: local-review
-description: Code review a pull request for bugs and CLAUDE.md compliance. MUST use when asked to review code.
+description: Code review the current PR (or branch diff against main) for bugs, security, and AGENTS.md compliance. MUST use when asked to review code.
---
-# Local Code Review Skill
+# Local Code Review
-Review a pull request for real bugs and CLAUDE.md compliance violations. This review targets HIGH SIGNAL issues only.
+Run the same review locally that the GitHub auto-review actions run on PRs (Claude / Codex / Pi). The review policy lives in `REVIEW.md`.
-## Review Philosophy
+**Why a subagent**: the review MUST run in a fresh context — not inline in the current session. If the user has been iterating on the diff, the main session has absorbed their reasoning and rationalizations, so it anchors and misses things CI catches. A subagent starts cold, like CI does.
-- **Only flag issues you are certain about.** If you are not sure an issue is real, do not flag it. False positives erode trust and waste reviewer time.
-- Think like a senior engineer doing a final review — flag things that would cause incidents, not things that are merely imperfect.
+## Steps
-## What to Flag
+1. **Determine the PR scope** (cheap, do this in the main session):
+ - If an argument is provided, treat it as a PR number or branch.
+ - Otherwise, detect from the current branch vs `main`.
+ - Confirm the PR/branch exists (`gh pr view ` or `git rev-parse `).
-- Code that won't compile or parse (syntax errors, type errors, missing imports)
-- Code that will definitely produce wrong results regardless of inputs
-- Clear, unambiguous CLAUDE.md violations (quote the exact rule being violated)
-- Security issues in introduced code (injection, auth bypass, data exposure)
-- Incorrect logic that will fail in production
+2. **Delegate the review to a fresh-context subagent** with a self-contained prompt. The prompt MUST include:
+ - The PR number or branch name to review.
+ - The instruction to read `REVIEW.md` first for the policy, then `AGENTS.md` files in directories touched by the diff.
+ - The exact output format (see below).
+ - Whether `--comment` was requested (so the subagent emits inline-comment payloads if needed).
+ - Any "Additional reviewer instructions" the user provided.
-## What NOT to Flag
+ - **Claude Code**: use the `Agent` tool with `subagent_type: branch-diff-reviewer` (read-only tools, purpose-built for this). If unavailable, fall back to `general-purpose`.
+ - **Codex / Pi**: if the CLI exposes a fresh-session subagent mechanism, use it. Otherwise tell the user to run the skill in a fresh CLI session and stop — running inline in the current session defeats the purpose.
-- Code style or quality concerns
-- Potential issues that depend on specific inputs or runtime state
-- Subjective suggestions or improvements
-- Pre-existing issues not introduced by this PR
-- Pedantic nitpicks a senior engineer wouldn't flag
-- Issues a linter or type checker will catch
-- General quality concerns unless explicitly prohibited in CLAUDE.md
-- Issues silenced via lint ignore comments
+3. **Receive the findings** from the subagent and relay them to the user verbatim. Do not re-summarize, re-judge, or filter — the whole point of fresh context is to surface what the main session would dismiss.
-## Execution Steps
+4. **Post comments if `--comment` was requested**: use the `gh` commands below with the subagent's output as the body. The main session does the posting because the subagent is read-only.
-1. **Determine the PR scope**:
- - If an argument is provided, use it as the PR number or branch
- - Otherwise, detect from the current branch vs main
- - Run `gh pr view` if a PR exists, or use `git diff main...HEAD`
+## Subagent prompt template
-2. **Find relevant CLAUDE.md files**:
- - Read the root `CLAUDE.md`
- - Check for CLAUDE.md files in directories containing changed files
+```
+Review against main per the policy in REVIEW.md.
-3. **Get the diff and metadata**:
- - `gh pr diff` or `git diff main...HEAD` for the full diff
- - `gh pr view` or `git log main..HEAD --oneline` for context
+Steps:
+1. Read REVIEW.md (repo root) for the full policy: severity triage, public-surface
+ checklist, AGENTS.md compliance, test coverage assessment.
+2. Read AGENTS.md (repo root) and any AGENTS.md in directories touched by the diff.
+3. Get the diff: `gh pr diff ` (if PR) or `git diff main...`.
+4. Get context: `gh pr view ` (if PR) or `git log main.. --oneline`.
+5. Read changed files only when the diff alone is insufficient to validate a finding.
+6. Self-validate each finding: "is this definitely a real issue a senior engineer
+ would flag?" Discard if uncertain.
+7. Output findings in the exact format below. Do not modify any files.
-4. **Read changed files** where the diff alone is insufficient to understand context
+
-5. **Review for**:
- - CLAUDE.md compliance — check each rule against the changed code
- - Bugs and logic errors — will this code work correctly?
- - Security issues — injection, auth, data exposure in new code
+
+Additionally emit a JSON array of inline comments suitable for the GitHub reviews
+API, one per finding that maps to a specific line:
+[{"path": "...", "line": N, "side": "RIGHT", "body": "[P1] ..."}, ...]
+```
-6. **Self-validate each finding**: Before reporting, ask yourself:
- - "Is this definitely a real issue, not a false positive?"
- - "Would a senior engineer flag this in review?"
- - If the answer to either is no, discard the finding
-
-7. **Output findings** to the terminal (default) or post as PR comments (with `--comment` flag)
-
-## Output Format
+## Output format
```
## Code review
+
+
Found N issues:
-1. ()
+1. [P0|P1|P2]
-2. ()
+2. [P0|P1|P2]
```
+End with a `Test coverage` section per the shared policy.
+
If no issues are found:
```
## Code review
-No issues found. Checked for bugs and CLAUDE.md compliance.
+Good to merge.
+
+No issues found. Checked for bugs, security, and AGENTS.md compliance.
```
-## Posting Comments (--comment flag)
+## Posting comments (`--comment`)
-If the user passes `--comment`, post findings as inline PR comments using:
+For a top-level PR comment:
```bash
-gh pr review --comment --body ""
+gh pr review --comment --body ""
```
-Or for inline comments on specific lines:
+For inline comments on specific lines (using the JSON the subagent emitted):
```bash
-gh api repos/{owner}/{repo}/pulls/{pr}/reviews -f body="" -f event="COMMENT" -f comments="[...]"
+gh api repos/{owner}/{repo}/pulls/{pr}/reviews \
+ -f body="" -f event="COMMENT" -f comments=""
```
diff --git a/.agents/skills/native-trigger/SKILL.md b/.agents/skills/native-trigger/SKILL.md
index 781e200d0c..ae38f70b32 100644
--- a/.agents/skills/native-trigger/SKILL.md
+++ b/.agents/skills/native-trigger/SKILL.md
@@ -607,7 +607,18 @@ In `frontend/src/lib/components/triggers/TriggersEditor.svelte`:
Add your service to the `nativeTriggerServices` map in `deleteDeployedTrigger()`. Native triggers use `NativeTriggerService.deleteNativeTrigger({ workspace, serviceName, externalId })` instead of the standard `path`-based delete.
-### Step 17: Update OpenAPI Spec and Regenerate Types
+### Step 17: Update `getUsedTriggers` for Sidebar Visibility
+
+The sidebar (`frontend/src/lib/components/sidebar/SidebarContent.svelte`) shows native-trigger links only if `$usedTriggerKinds` includes the service — without this, your trigger page will never appear in the nav bar even when triggers exist.
+
+1. **Backend** — add `{service}_used: bool` to the `UsedTriggers` struct and SELECT in `backend/windmill-api-workspaces/src/workspaces.rs::get_used_triggers()`:
+ ```rust
+ EXISTS(SELECT 1 FROM native_trigger WHERE workspace_id = $1 AND service_name = '{service}'::native_trigger_service) AS "{service}_used!"
+ ```
+2. **OpenAPI** — add `{service}_used: boolean` to the response schema for `GET /w/{workspace}/workspaces/used_triggers` (under both `properties` and `required`).
+3. **Layout** — in `frontend/src/routes/(root)/(logged)/+layout.svelte::loadUsedTriggerKinds()`, destructure `{service}_used` and push `'{service}'` to `usedKinds`.
+
+### Step 18: Update OpenAPI Spec and Regenerate Types
Add to `JobTriggerKind` enum in `backend/windmill-api/openapi.yaml`, then:
diff --git a/.agents/skills/pr/SKILL.md b/.agents/skills/pr/SKILL.md
index 2efcc4e0a6..ef52d6e110 100644
--- a/.agents/skills/pr/SKILL.md
+++ b/.agents/skills/pr/SKILL.md
@@ -1,5 +1,6 @@
---
name: pr
+user_invocable: true
description: Open a draft pull request on GitHub. MUST use when you want to create/open a PR.
---
@@ -50,22 +51,22 @@ The body MUST be explicit about what changed. Structure:
## Test plan
- [ ]
- [ ]
-
----
-Generated with [Claude Code](https://claude.com/claude-code)
```
+The harness/tooling that invoked the skill may add its own attribution trailer; the skill itself does not prescribe one.
+
## Execution Steps
1. Run `git status` to check for uncommitted changes
2. Run `git log main..HEAD --oneline` to see all commits in this branch
3. Run `git diff main...HEAD` to see the full diff against main
-4. Check if remote branch exists and is up to date:
+4. **Invoke the `local-review` skill** before creating the PR (`/local-review` in Claude Code, `$local-review` in Codex, `pi --skill local-review` / `/skill:local-review` in Pi). If issues are found, fix them and commit before proceeding. Do not skip this step.
+5. Check if remote branch exists and is up to date:
```bash
git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null || echo "no upstream"
```
-5. Push to remote if needed: `git push -u origin HEAD`
-6. Create draft PR using gh CLI:
+6. Push to remote if needed: `git push -u origin HEAD`
+7. Create draft PR using gh CLI:
```bash
gh pr create --draft --title ": " --body "$(cat <<'EOF'
## Summary
@@ -78,13 +79,10 @@ Generated with [Claude Code](https://claude.com/claude-code)
## Test plan
- [ ]
- [ ]
-
- ---
- Generated with [Claude Code](https://claude.com/claude-code)
EOF
)"
```
-7. Return the PR URL to the user
+8. Return the PR URL to the user
## EE Companion PR (when `*_ee.rs` files were modified)
@@ -100,9 +98,6 @@ Follow the full EE PR workflow in `docs/enterprise.md`. The key PR-specific deta
```bash
gh pr create --draft --repo windmill-labs/windmill-ee-private --title ": " --body "$(cat <<'EOF'
Companion PR for windmill-labs/windmill#
-
- ---
- Generated with [Claude Code](https://claude.com/claude-code)
EOF
)"
```
diff --git a/.agents/skills/refine/SKILL.md b/.agents/skills/refine/SKILL.md
index b96e97e8a2..aaf747cd29 100644
--- a/.agents/skills/refine/SKILL.md
+++ b/.agents/skills/refine/SKILL.md
@@ -1,5 +1,6 @@
---
name: refine
+user_invocable: true
description: End-of-session reflection. Reviews friction encountered during the session and proposes updates to docs/ to capture lessons learned.
---
diff --git a/.agents/skills/svelte-frontend/SKILL.md b/.agents/skills/svelte-frontend/SKILL.md
index 57cac70302..b0c4b39939 100644
--- a/.agents/skills/svelte-frontend/SKILL.md
+++ b/.agents/skills/svelte-frontend/SKILL.md
@@ -78,3 +78,7 @@ Use the Svelte MCP tools when working on Svelte code:
2. **get-documentation**: Fetch relevant sections based on use_cases
3. **svelte-autofixer**: MUST use on all Svelte code before finalizing — keep calling until no issues
4. **playground-link**: Only after user confirms and code was NOT written to project files
+
+## Verifying in the Browser
+
+After changing Svelte code, use the **Playwright MCP** (`mcp__playwright__*`) to drive the running frontend and confirm the change works. See AGENTS.md → "Verifying Frontend Changes" for the full flow. Use `playwright` (headless) on devboxes; `playwright-headed` when a display is available.
diff --git a/.claude/hooks/guard-main-branch.sh b/.claude/hooks/guard-main-branch.sh
index c7eeea9475..7a3a8189a0 100755
--- a/.claude/hooks/guard-main-branch.sh
+++ b/.claude/hooks/guard-main-branch.sh
@@ -16,6 +16,23 @@ command="$(echo "$input" | jq -r '.tool_input.command // empty')"
if [[ "$command" =~ ^git\ (push|reset|revert|checkout|merge|rebase|commit|add) ]]; then
branch="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
if [[ "$branch" == "main" ]]; then
- echo "BLOCK: You are on the main branch. Create or switch to a feature branch first."
+ echo "BLOCK: You are on the main branch. Create or switch to a feature branch first." >&2
+ exit 2
+ fi
+fi
+
+# Block force-push targeting main from any branch.
+if [[ "$command" =~ ^git[[:space:]]+push([[:space:]]|$) ]]; then
+ has_force=false
+ if [[ "$command" =~ (--force([[:space:]]|=|$)|--force-with-lease|[[:space:]]-f([[:space:]]|$)) ]]; then
+ has_force=true
+ fi
+ # `+ref` refspec syntax is also a force push.
+ if [[ "$command" =~ [[:space:]]\+[A-Za-z] ]]; then
+ has_force=true
+ fi
+ if $has_force && [[ "$command" =~ (^|[[:space:]:])\+?main([[:space:]]|$) ]]; then
+ echo "BLOCK: Force-push to main is not allowed via Claude. Run it yourself if you really mean to." >&2
+ exit 2
fi
fi
diff --git a/.claude/review-prompt.md b/.claude/review-prompt.md
index 6814089bea..b3b6df0d74 100644
--- a/.claude/review-prompt.md
+++ b/.claude/review-prompt.md
@@ -1,25 +1,4 @@
-# Code Review Instructions
+# Claude output format
-Review this pull request and provide comprehensive feedback.
-
-## Focus Areas
-
-- **Code quality and best practices** — does the code follow established patterns?
-- **Potential bugs or issues** — will this code work correctly in all cases?
-- **Performance considerations** — are there unnecessary allocations, N+1 queries, or bottlenecks?
-- **Security implications** — injection, auth bypass, data exposure?
-
-## CLAUDE.md Compliance
-
-Read all relevant CLAUDE.md files (root and in directories containing changed files). Check each rule against the changed code. Quote the exact rule when flagging a violation.
-
-## Review Guidelines
-
-- Provide detailed feedback using inline comments for specific issues
-- Use top-level comments for general observations or praise
-- Only flag issues introduced by this PR, not pre-existing problems
-- Self-validate each finding: "Is this definitely a real issue?" If uncertain, discard it
-
-## Testing Instructions
-
-At the end of your review, add complete instructions to reproduce the added changes through the app interface. These instructions will be given to a tester so they can verify the changes. It should be a short descriptive text (not a step-by-step or a list) on how to navigate the app (what page, what action, what input, etc.) to see the changes.
+- Use inline comments at the relevant lines for specific issues.
+- Use a top-level comment for the summary, severity-tagged finding list, AGENTS.md compliance check, and the test-coverage assessment.
diff --git a/.claude/settings.json b/.claude/settings.json
index 0596b17e91..ca8d9a898d 100644
--- a/.claude/settings.json
+++ b/.claude/settings.json
@@ -44,7 +44,25 @@
"Bash(git merge:*)",
"Bash(git rebase:*)",
"Bash(git add:*)",
- "Bash(git commit:*)"
+ "Bash(git commit:*)",
+ "Read(/tmp/**)",
+ "Write(/tmp/**)",
+ "Edit(/tmp/**)",
+ "Bash(rm:/tmp/*)",
+ "Bash(rm:/tmp/**)",
+ "Bash(rmdir:/tmp/*)",
+ "Bash(mkdir:/tmp/*)",
+ "Bash(mkdir:/tmp/**)",
+ "Bash(cp:/tmp/*)",
+ "Bash(cp:/tmp/**)",
+ "Bash(mv:/tmp/*)",
+ "Bash(mv:/tmp/**)",
+ "Bash(touch:/tmp/*)",
+ "Bash(touch:/tmp/**)",
+ "Bash(chmod:/tmp/*)",
+ "Bash(chmod:/tmp/**)",
+ "Bash(tar * /tmp/*)",
+ "Bash(unzip * /tmp/*)"
],
"deny": [
"Read(.env)",
@@ -55,7 +73,10 @@
"Read(**/*.pem)",
"Read(**/*.key)",
"Read(**/credentials.json)",
- "Read(**/*secret*)",
+ "Read(**/.secret*)",
+ "Read(**/.secrets*)",
+ "Read(**/*.secret)",
+ "Read(**/*.secrets)",
"Edit(.env)",
"Edit(.env.*)",
"Edit(**/.env)",
@@ -69,7 +90,13 @@
"Bash(chown:*)",
"Bash(truncate:*)",
"Bash(shred:*)",
- "Bash(unlink:*)"
+ "Bash(unlink:*)",
+ "mcp__claude_ai_Stripe",
+ "mcp__claude_ai_Gmail",
+ "mcp__claude_ai_Google_Calendar",
+ "mcp__claude_ai_Google_Drive",
+ "mcp__claude_ai_Slack",
+ "mcp__claude_ai_Linear"
]
},
"enableAllProjectMcpServers": true,
diff --git a/.claude/skills/adding-a-trigger/SKILL.md b/.claude/skills/adding-a-trigger/SKILL.md
deleted file mode 100644
index 7d8643b862..0000000000
--- a/.claude/skills/adding-a-trigger/SKILL.md
+++ /dev/null
@@ -1,267 +0,0 @@
----
-name: adding-a-trigger
-description: Checklist for adding a new TriggerCrud-based trigger type to Windmill (Azure, GCP, Kafka, etc.). Use when wiring a new trigger kind across backend, frontend, CLI, and capture infrastructure.
----
-
-# Skill: Adding a New Trigger Type
-
-Use this skill when adding a trigger kind that implements `TriggerCrud` (Kafka, GCP, Azure, MQTT, SQS, NATS, Postgres, Email…). For native triggers (Nextcloud, Google Drive — things wired through `windmill-native-triggers`), use the `native-trigger` skill instead.
-
-The goal of this doc is to enumerate every file that needs to change. Missing any one of them leads to silent regressions: sync drops the trigger, capture button does nothing, workspace forks lose it, sidebar counters undercount. Follow the checklist top-to-bottom — each section is independent enough to be validated on its own.
-
-Throughout this doc, substitute `{kind}` for the new trigger kind (`azure`, `kafka`, …), `{Kind}` for PascalCase (`Azure`, `Kafka`), `{KIND}` for SCREAMING (`AZURE`, `KAFKA`).
-
-## Reference implementations
-
-- **GCP** — closest analogue to Azure. Has push + pull, OIDC auth, ARM-like resource paths, capture handler. Grep for `gcp_trigger` / `GcpTrigger`.
-- **Kafka** — simpler (pull-only, streaming). Good for trivial integrations.
-- **Azure** — most recently added (2026). Shared-secret push auth, Event Grid namespaces + basic topics, ARM resource discovery, Namespace-pull data-plane. Grep for `azure_trigger` / `AzureTrigger`.
-
-## 1. Database migration
-
-Create a migration: `cargo sqlx migrate add -r add_{kind}_trigger` from `backend/`. Never write timestamps manually.
-
-The `up.sql` usually defines:
-- An optional enum type (e.g. `AZURE_MODE`) if the trigger has sub-kinds
-- The `{kind}_trigger` table with at minimum these columns (mirrored from kafka/gcp):
- - primary: `(workspace_id, path)`
- - `script_path`, `is_flow`, `enabled`, `mode`, `permissioned_as`, `edited_by`, `email`
- - `edited_at`, `error`, `server_id`, `last_server_ping`
- - `error_handler_path`, `error_handler_args jsonb`, `retry jsonb`
- - trigger-specific fields
-- Indexes on foreign keys + any frequently-filtered columns
-- Foreign key to `workspace`
-
-Down migration drops the table and any enum types.
-
-## 2. Backend crate (`windmill-trigger-{kind}`)
-
-Create a new crate under `backend/windmill-trigger-{kind}/` with:
-
-- `Cargo.toml`: features `enterprise`, `private` if EE, standard deps
-- `src/lib.rs`: `pub use mod_ee::*;` behind `#[cfg(all(feature = "enterprise", feature = "private"))]`
-- `src/mod_ee.rs`: core types + helpers
-- `src/handler_ee.rs`: `TriggerCrud` impl + route handlers
-- `src/listener_ee.rs`: (only if streaming/pull-based) `Listener` trait impl
-
-Required in `mod_ee.rs`:
-- `{Kind}Config` struct (persisted shape, `FromRow`)
-- `{Kind}ConfigRequest` struct (what API receives — usually similar to Config but with validation fields)
-- `{Kind}Trigger` unit struct (implements the traits)
-- `impl TriggerJobArgs for {Kind}Trigger` — sets `TRIGGER_KIND`, `Payload`, `v1_payload_fn`
-
-Required in `handler_ee.rs`:
-- `#[async_trait] impl TriggerCrud for {Kind}Trigger` with:
- - `type Trigger = Trigger<{Kind}Config>`
- - `type TriggerConfigRequest = {Kind}ConfigRequest`
- - `const ROUTE_PREFIX: &'static str = "/{kind}_triggers";`
- - `const TABLE_NAME`, `ADDITIONAL_SELECT_FIELDS`
- - `get_deployed_object`, `validate_config`, `create_trigger`, `update_trigger`, `delete_trigger`, `test_connection`
- - `additional_routes` (optional — mount extra endpoints for things like ARM resource listing, topic discovery)
-
-Register the crate in `backend/Cargo.toml` as a workspace member and as a dep of `windmill-api` behind the feature flag.
-
-## 3. Wire into `windmill-api` (feature-gated everywhere)
-
-**`backend/windmill-api/src/triggers/handler.rs`** — mount the trigger crate:
-```rust
-#[cfg(all(feature = "enterprise", feature = "{kind}_trigger", feature = "private"))]
-{
- use crate::triggers::{kind}::{Kind}Trigger;
- router = router.nest({Kind}Trigger::ROUTE_PREFIX, complete_trigger_routes({Kind}Trigger));
-}
-```
-
-**`backend/windmill-api/src/triggers/{kind}/mod.rs`** — re-export the crate:
-```rust
-pub use windmill_trigger_{kind}::*;
-```
-
-**`backend/windmill-api/src/lib.rs`** — if the trigger receives inbound pushes, add a webhook route:
-```rust
-.nest("/{kind}/w/{workspace_id}", {
- #[cfg(all(feature = "enterprise", feature = "{kind}_trigger", feature = "private"))]
- { triggers::{kind}::handler_oss::{kind}_push_route_handler() }
- #[cfg(not(...))]
- { Router::new() }
-})
-```
-
-## 4. `TriggerKind` enum (`backend/windmill-types/src/triggers.rs`)
-
-Already has slots for most triggers but verify your variant exists:
-- Add `{Kind}` to the `TriggerKind` enum
-- Add match arm in `to_key()`
-- Add match arm in `from_str`
-- Add match arm in `JobTriggerKind` (if jobs need kind tagging)
-
-## 5. OpenAPI (`backend/windmill-api/openapi.yaml`)
-
-This file is huge and the single most-forgotten place. Add:
-
-- `/w/{workspace}/{kind}_triggers/create` + `/update/{path}` + `/delete/{path}` + `/get/{path}` + `/list` + `/exists/{path}` + `/setmode/{path}` + `/test` paths (mirror gcp section)
-- Any `additional_routes` your handler exposes (resource discovery, etc.)
-- Schemas: `{Kind}Trigger`, `{Kind}TriggerData`, `{Kind}Mode` (if enum), `{Kind}DeliveryConfig`, helper request/response types
-- Add `{kind}` to `CaptureTriggerKind` enum
-- Add `{kind}_used: boolean` to the `UsedTriggers` response schema
-
-Regenerate frontend client: `npm run generate-backend-client` from `frontend/`.
-
-## 6. `UsedTriggers` + workspace export
-
-**`backend/windmill-api-workspaces/src/workspaces.rs`** — add `{kind}_used: bool` to the `UsedTriggers` struct and add an `EXISTS(SELECT 1 FROM {kind}_trigger …)` to the `get_used_triggers` query.
-
-**`backend/windmill-api/src/workspaces_export.rs`** — add export block mirroring gcp's (export lists all triggers, serializes them to YAML/JSON). The block re-uses the `trigger_ignore_keys` variable so the new kind automatically participates in fork-export stripping (`mode` field is omitted when the source workspace is a fork — keeps fork→parent merges from flipping the parent's enabled state).
-
-**Fork cloning (`clone_triggers_and_schedules` in workspaces.rs)** — add an `INSERT INTO {kind}_trigger ... SELECT ...` block that copies all rows from the parent workspace, forcing `mode = 'disabled'::TRIGGER_MODE`. Always runs at fork creation; forgetting this means users can't carry `{kind}` triggers into their forks.
-
-## 6.5 Hardcoded trigger-kind arrays (silent-failure hotspots)
-
-Several files keep **hardcoded arrays** of trigger kind strings. Miss one and ACL checks / user offboarding / trash drop your kind:
-
-- **`backend/windmill-api-groups/src/granular_acls.rs`** — `KINDS: [&str; N]`. **Increment N** (the compile error is cryptic otherwise). Controls which kinds accept granular ACL operations.
-- **`backend/windmill-api-users/src/users.rs`** (`extra_perms_tables`) — which tables get `extra_perms` entries cleaned when a user is deleted.
-- **`backend/windmill-api/src/offboarding.rs`** — three separate arrays (enumeration, fork-copy, and delete paths). **All three** need the new kind.
-- **`backend/windmill-api/src/trash.rs`** — `valid_tables` for the trash / restore API.
-- **`backend/windmill-git-sync/src/lib.rs`** — add a test assertion for `DeployedObject::{Kind}Trigger.get_kind() == "{kind}_trigger"` (the `get_kind` match arm itself lives in the enum impl — already required by the Rust compiler).
-- **`backend/windmill-api-auth/src/scopes.rs`** — add the `{Kind}Triggers` variant to `ScopeDomain` enum + `as_str` match + `from_str` match. Required for the OAuth/token system to recognise `{kind}_triggers:read|write` scopes.
-- **`backend/windmill-api/src/token.rs`** (`build_trigger_scope_domains` → `TRIGGER_DOMAINS`) — add `("{kind}_triggers", "{Kind display name}")` so the CreateToken UI's scope selector surfaces the `read` / `write` checkboxes.
-
-**OpenAPI enums** to extend (do NOT forget — generated client will allow it but server rejects as 400):
-- `CaptureTriggerKind` enum
-- Three `kind` enums under `/w/{workspace}/acls/{get,add,remove}/{kind}/{path}` (yes, same list repeated three times)
-
-After editing any of these, run a full `cargo check` with your feature flag + `gcp_trigger` + other core flags — the `KINDS: [&str; N]` length mismatch only surfaces when the crate compiles.
-
-## 7. Capture infrastructure (`backend/windmill-api/src/capture.rs`)
-
-If the trigger supports push delivery, it also needs a capture endpoint so users can test it:
-
-- `{Kind}TriggerConfig` struct (gated by feature flags)
-- `TriggerConfig::{Kind}` variant
-- `set_{kind}_trigger_config` function (creates the subscription/equivalent pointing at the capture URL — use your `manage_{kind}_subscription` helper with `trigger_mode=false`)
-- Both real + no-op versions behind feature gates
-- `TriggerKind::{Kind} => set_{kind}_trigger_config(...)` arm in `set_config`
-- `{kind}_payload` async handler — validates auth (if any), processes payload, calls `insert_capture_payload`
-- Route: `.route("/{kind}/{runnable_kind}/{*path}", post({kind}_payload))` inside `workspaced_unauthed_service` — and expand the surrounding `#[cfg(any(...))]` to include your feature flag
-
-## 8. CLI (`cli/`) — easy to miss, breaks sync silently
-
-Check all of these:
-
-**`cli/src/types.ts`:**
-- Add `"{kind}"` to `TRIGGER_TYPES` array
-- Add `"{kind}_trigger"` to `getTypeStrFromPath` return union
-- Add match case in `getTypeStrFromPath`'s `typeEnding ===` chain
-- Add `pushTrigger("{kind}", ...)` branch in `pushObj`
-
-**`cli/src/commands/trigger/trigger.ts`:**
-- Import `{Kind}Trigger` type
-- Add `{kind}: {Kind}Trigger` to the `Trigger` type map
-- Add `{kind}: wmill.get{Kind}Trigger`, `update{Kind}Trigger`, `create{Kind}Trigger` to each function map
-- Add `{kind}: { ... }` template to `triggerTemplates`
-- Add `list{Kind}Triggers` call + spread in the `list` aggregation
-- Update `--kind` option descriptions to mention the new kind
-
-**`cli/src/commands/sync/sync.ts`:**
-- Add `path.endsWith(".{kind}_trigger" + ext)` in the file-type filter
-- Add `typ == "{kind}_trigger"` in `getTypeOrder`
-- Add `"{kind}_trigger"` to the delete-suffix regex (~line 3092)
-- Add a `case "{kind}_trigger"` in the delete switch
-
-**`cli/src/guidance/skills.ts`** — **DO NOT EDIT DIRECTLY**. It's auto-generated by `system_prompts/generate.py`. Instead:
-- Edit `system_prompts/utils.py` → append `('{Kind}Trigger', '{kind}_trigger')` to the `SCHEMA_MAPPINGS['triggers']` list (this is the master list — the one in `generate.py` is duplicated and `utils.py` wins)
-- Then run `python3 system_prompts/generate.py` — it regenerates `cli/src/guidance/skills.ts` with the schema extracted from `backend/windmill-api/openapi.yaml`
-- Commit the regenerated file
-
-## 9. Frontend — editor + drawer
-
-Under `frontend/src/lib/components/triggers/{kind}/`:
-
-- `{Kind}TriggerPanel.svelte` — the tile shown in the triggers listing
-- `{Kind}TriggerEditor.svelte` — outer drawer wrapper
-- `{Kind}TriggerEditorInner.svelte` — state + business logic; must expose:
- - `openEdit(path, isFlow, defaultValues?)` method
- - `isEditor` prop, `onConfigChange` + `onCaptureConfigChange` callbacks
- - `get{Kind}Config()` + `get{Kind}CaptureConfig()` helpers
- - `captureConfig = $derived.by(untrack(() => isEditor) ? get{Kind}CaptureConfig : () => ({}))`
- - `$effect(() => { const args = [captureConfig, isValid] as const; untrack(() => onCaptureConfigChange?.(...args)) })`
-- `{Kind}TriggerEditorConfigSection.svelte` — form fields; use design-system components (`TextInput`, `Select`, `Toggle`, `ToggleButtonGroup`), never raw ` `
-- `{Kind}Capture.svelte` — capture panel; wraps `CaptureSection` with `captureType="{kind}"`
-- `utils.ts` — `requestBody` builders and any trigger-type-specific helpers
-
-## 10. Frontend — global integration
-
-Easy to miss:
-
-- **`frontend/src/lib/components/triggers.ts`** — add `'{kind}'` to the `TriggerKind` union
-- **`frontend/src/lib/components/triggers/CaptureWrapper.svelte`**:
- - Import `{Kind}Capture`
- - Add to `isStreamingCapture()` array (streaming = pull-style; push-style is typically `false`)
- - Add `{:else if captureType === '{kind}'}` branch with the `<{Kind}Capture>` render
-- **`frontend/src/lib/components/sidebar/SidebarContent.svelte`** — import the icon, add the nav entry
-- **`frontend/src/lib/components/sidebar/OperatorMenu.svelte`** — add the operator-mode entry
-- **`frontend/src/routes/(root)/(logged)/+layout.svelte`** — destructure `{kind}_used` from `/get_used_triggers` response, push `'{kind}'` into `usedKinds`
-- **`frontend/src/lib/components/search/GlobalSearchModal.svelte`** — import icon, add "Go to {Kind} ..." entry
-- **`frontend/src/lib/components/offboarding-utils.ts`** — add mappings `{kind}_trigger: '{kind}_triggers'` and `{kind}_trigger: '{kind} trigger'`
-- **`frontend/src/lib/components/icons/{Kind}Icon.svelte`** — single-path SVG, `fill={color ?? 'currentColor'}`, `size` prop default 16 (match existing icons — don't hardcode colors, don't use `width`/`height` props)
-- **`frontend/src/routes/(root)/(logged)/{kind}_triggers/+page.svelte`** — listing page (mirror `gcp_triggers/+page.svelte` for push+pull, `kafka_triggers` for pure streaming)
-- **`frontend/src/lib/components/CompareWorkspaces.svelte`** — workspace fork / compare tool. Needs: service import, editor import, `{kind}Editor` `$state`, `case '{kind}'` in `openTriggerDetails()`, entry in `triggerServices` object (list/delete/normalize), and `<{Kind}TriggerEditor bind:this={{kind}Editor} />` in the template
-
-## 10.5 AI system prompts (`system_prompts/`)
-
-- **`system_prompts/utils.py`** — append `('{Kind}Trigger', '{kind}_trigger')` to `SCHEMA_MAPPINGS['triggers']` (master list used by code generation + CLI skills)
-- **`system_prompts/generate.py`** — also has a duplicated `schema_types` list (~line 903) for the AI `triggers` skill content. Add `('{Kind}Trigger', '{kind}_trigger')` there too
-- **`system_prompts/generate.py`** `schema_names` (~line 1192) — add `'{Kind}Trigger'` (add `'New{Kind}Trigger'` only if the OpenAPI declares one; GCP and Azure don't)
-- Run `python3 system_prompts/generate.py` — this rewrites `cli/src/guidance/skills.ts` and all `auto-generated/` docs. Commit the regenerated files
-
-## 11. Validation
-
-Run all of these before declaring done:
-
-```bash
-# Backend
-cd backend
-cargo check --features enterprise,{kind}_trigger,private # minimal
-cargo check --features enterprise,azure_trigger,private,gcp_trigger,http_trigger,mqtt_trigger,postgres_trigger,sqs_trigger,kafka,nats,smtp,websocket # full
-
-# SQLx offline data (never run `cargo sqlx prepare` directly — use the wrapper)
-./update_sqlx.sh
-
-# Frontend
-cd frontend
-npm run generate-backend-client
-npm run check:fast
-```
-
-Smoke test in the UI: create a trigger, save, check it appears in sidebar + search, delete, re-create via CLI `wmill sync`.
-
-## 12. Common pitfalls
-
-- **Forgetting feature gates in `workspaced_unauthed_service()`** — the surrounding `#[cfg(any(...))]` expression must include your feature flag, not just the inner `#[cfg]` on the route
-- **`.route(path, ...).route(path, ...)` with same path and different methods** — older axum replaced; use `.route(path, post(h1).options(h2))` to chain methods on the same `MethodRouter`
-- **`on:event` directives** — legacy Svelte 4, no-op in runes mode. Use callback props (`onSelected`, `onConfigChange`)
-- **`$bindable(default_value)` on optional props** — banned by project CLAUDE.md. Use `$bindable()` + `$derived(prop ?? default)` instead
-- **CORS layer intercepting OPTIONS** — tower-http CorsLayer short-circuits OPTIONS before reaching your handler. For server-to-server webhook endpoints, drop the CORS layer entirely (CORS is browser-only)
-- **DeliveryAttributeMappings / custom headers for auth** — prefer HMAC or sha256-hashed shared secrets over opaque JWTs when the provider doesn't support signed tokens natively. Store only the hash; regenerate secret on every save
-- **ARM / API resource-listing cascades** — if the trigger's resource type is deep (Azure: subscription → RG → namespace → topic), offer dropdowns in the UI populated from the provider's APIs using the user's credential resource
-- **Clearing stale selections on dependency change** — when a dropdown's underlying data reloads (e.g., user changes SP or edition), clear selections that no longer match the new list
-- **Workspace-scoped tag compatibility** — if the trigger has tags, verify forked workspaces handle them (see commit `0773b5bc85` for a historical fix)
-
-## 13. EE file split
-
-If the trigger is enterprise-only, the code lives in `windmill-ee-private__worktrees/.../windmill-trigger-{kind}/src/*_ee.rs` and is symlinked into the OSS tree. The `windmill-ee-private__worktrees/` directory holds the real files; changes propagate via symlinks. See `docs/enterprise.md` for the workflow.
-
-## 14. Final checklist before PR
-
-- [ ] Migration up/down tested (revert + re-apply)
-- [ ] `./update_sqlx.sh` committed the updated `.sqlx/` offline data
-- [ ] `cargo check` passes with your feature flag + with all trigger features
-- [ ] `npm run check:fast` passes
-- [ ] Trigger visible in sidebar with correct icon weight (not oversized/colored — use `currentColor`)
-- [ ] Create, edit, delete flow all work in the UI
-- [ ] Capture button works (if push-capable)
-- [ ] Trigger appears in `/get_used_triggers` → sidebar pulse
-- [ ] `wmill sync pull` + `wmill sync push` both round-trip the trigger
-- [ ] `wmill trigger list` includes it
-- [ ] OpenAPI schemas are complete (no `null` in generated types)
diff --git a/.claude/skills/adding-a-trigger/SKILL.md b/.claude/skills/adding-a-trigger/SKILL.md
new file mode 120000
index 0000000000..a2060ad897
--- /dev/null
+++ b/.claude/skills/adding-a-trigger/SKILL.md
@@ -0,0 +1 @@
+../../../.agents/skills/adding-a-trigger/SKILL.md
\ No newline at end of file
diff --git a/.claude/skills/commit/SKILL.md b/.claude/skills/commit/SKILL.md
deleted file mode 100644
index 2094dbab06..0000000000
--- a/.claude/skills/commit/SKILL.md
+++ /dev/null
@@ -1,60 +0,0 @@
----
-name: commit
-user_invocable: true
-description: Create a git commit with conventional commit format. MUST use anytime you want to commit changes.
----
-
-# Git Commit Skill
-
-Create a focused, single-line commit following conventional commit conventions.
-
-## Instructions
-
-1. **Analyze changes**: Run `git status` and `git diff` to understand what was modified
-2. **Stage only modified files**: Add files individually by name. NEVER use `git add -A` or `git add .`
-3. **Write commit message**: Follow the conventional commit format as a single line
-
-## Conventional Commit Format
-
-```
-:
-```
-
-### Types
-- `feat`: New feature or capability
-- `fix`: Bug fix
-- `refactor`: Code change that neither fixes a bug nor adds a feature
-- `docs`: Documentation only changes
-- `style`: Formatting, missing semicolons, etc (no code change)
-- `test`: Adding or correcting tests
-- `chore`: Maintenance tasks, dependency updates, etc
-- `perf`: Performance improvement
-
-### Rules
-- Message MUST be a single line (no multi-line messages)
-- Description should be lowercase, imperative mood ("add" not "added")
-- No period at the end
-- Keep under 72 characters total
-
-### Examples
-```
-feat: add token usage tracking for AI providers
-fix: resolve null pointer in job executor
-refactor: extract common validation logic
-docs: update API endpoint documentation
-chore: upgrade sqlx to 0.7
-```
-
-## Execution Steps
-
-1. Run `git status` to see all changes
-2. Run `git diff` to understand the changes in detail
-3. Run `git log --oneline -5` to see recent commit style
-4. Stage ONLY the modified/relevant files: `git add ...`
-5. Create the commit with conventional format:
- ```bash
- git commit -m ":
-
- Co-Authored-By: Claude Opus 4.5 "
- ```
-6. Run `git status` to verify the commit succeeded
diff --git a/.claude/skills/commit/SKILL.md b/.claude/skills/commit/SKILL.md
new file mode 120000
index 0000000000..11493a3d1e
--- /dev/null
+++ b/.claude/skills/commit/SKILL.md
@@ -0,0 +1 @@
+../../../.agents/skills/commit/SKILL.md
\ No newline at end of file
diff --git a/.claude/skills/local-review/SKILL.md b/.claude/skills/local-review/SKILL.md
deleted file mode 100644
index 0399ad7294..0000000000
--- a/.claude/skills/local-review/SKILL.md
+++ /dev/null
@@ -1,69 +0,0 @@
----
-name: local-review
-user_invocable: true
-description: Code review a pull request for bugs and CLAUDE.md compliance. MUST use when asked to review code.
----
-
-# Local Code Review Skill
-
-Run the same review locally that the GitHub Claude Auto Review action runs on PRs. The shared review instructions live in `.claude/review-prompt.md` — read that file first and follow its instructions.
-
-## Execution Steps
-
-1. **Read `.claude/review-prompt.md`** for the review criteria and focus areas
-
-2. **Determine the PR scope**:
- - If an argument is provided, use it as the PR number or branch
- - Otherwise, detect from the current branch vs main
- - Run `gh pr view` if a PR exists, or use `git diff main...HEAD`
-
-3. **Get the diff and metadata**:
- - `gh pr diff` or `git diff main...HEAD` for the full diff
- - `gh pr view` or `git log main..HEAD --oneline` for context
-
-4. **Read changed files** where the diff alone is insufficient to understand context
-
-5. **Apply the review instructions from `.claude/review-prompt.md`**
-
-6. **Self-validate each finding**: Before reporting, ask yourself:
- - "Is this definitely a real issue, not a false positive?"
- - "Would a senior engineer flag this in review?"
- - If the answer to either is no, discard the finding
-
-7. **Output findings** to the terminal (default) or post as PR comments (with `--comment` flag)
-
-## Output Format
-
-```
-## Code review
-
-Found N issues:
-
-1. ()
-
-
-2. ()
-
-```
-
-If no issues are found:
-
-```
-## Code review
-
-No issues found. Checked for bugs and CLAUDE.md compliance.
-```
-
-## Posting Comments (--comment flag)
-
-If the user passes `--comment`, post findings as inline PR comments using:
-
-```bash
-gh pr review --comment --body ""
-```
-
-Or for inline comments on specific lines:
-
-```bash
-gh api repos/{owner}/{repo}/pulls/{pr}/reviews -f body="" -f event="COMMENT" -f comments="[...]"
-```
diff --git a/.claude/skills/local-review/SKILL.md b/.claude/skills/local-review/SKILL.md
new file mode 120000
index 0000000000..8072aff10d
--- /dev/null
+++ b/.claude/skills/local-review/SKILL.md
@@ -0,0 +1 @@
+../../../.agents/skills/local-review/SKILL.md
\ No newline at end of file
diff --git a/.claude/skills/native-trigger/SKILL.md b/.claude/skills/native-trigger/SKILL.md
deleted file mode 100644
index ae38f70b32..0000000000
--- a/.claude/skills/native-trigger/SKILL.md
+++ /dev/null
@@ -1,793 +0,0 @@
----
-name: native-trigger
-description: Guidance for adding native trigger services to Windmill. Use when implementing or modifying native trigger integrations across the backend and frontend.
----
-
-# Skill: Adding Native Trigger Services
-
-This skill provides comprehensive guidance for adding new native trigger services to Windmill. Native triggers allow external services (like Nextcloud, Google Drive, etc.) to trigger Windmill scripts/flows via webhooks or push notifications.
-
-## Architecture Overview
-
-The native trigger system consists of:
-
-1. **Database Layer** - PostgreSQL tables and enum types
-2. **Backend Rust Implementation** - Core trait, handlers, and service modules in the `windmill-native-triggers` crate
-3. **Frontend Svelte Components** - Configuration forms and UI components
-
-### Key Files
-
-| Component | Path |
-|-----------|------|
-| Core module with `External` trait | `backend/windmill-native-triggers/src/lib.rs` |
-| Generic CRUD handlers | `backend/windmill-native-triggers/src/handler.rs` |
-| Background sync logic | `backend/windmill-native-triggers/src/sync.rs` |
-| OAuth/workspace integration | `backend/windmill-native-triggers/src/workspace_integrations.rs` |
-| Re-export shim (windmill-api) | `backend/windmill-api/src/native_triggers/mod.rs` |
-| TriggerKind enum | `backend/windmill-common/src/triggers.rs` |
-| JobTriggerKind enum | `backend/windmill-common/src/jobs.rs` |
-| Frontend service registry | `frontend/src/lib/components/triggers/native/utils.ts` |
-| Frontend trigger utilities | `frontend/src/lib/components/triggers/utils.ts` |
-| Trigger badges (icons + counts) | `frontend/src/lib/components/graph/renderers/triggers/TriggersBadge.svelte` |
-| Workspace integrations UI | `frontend/src/lib/components/workspaceSettings/WorkspaceIntegrations.svelte` |
-| OAuth config form component | `frontend/src/lib/components/workspaceSettings/OAuthClientConfig.svelte` |
-| OpenAPI spec | `backend/windmill-api/openapi.yaml` |
-| Reference: Nextcloud module | `backend/windmill-native-triggers/src/nextcloud/` |
-| Reference: Google module | `backend/windmill-native-triggers/src/google/` |
-
-### Crate Structure
-
-The native trigger code lives in the `windmill-native-triggers` crate (`backend/windmill-native-triggers/`). The `windmill-api` crate re-exports everything via a shim:
-
-```rust
-// backend/windmill-api/src/native_triggers/mod.rs
-pub use windmill_native_triggers::*;
-```
-
-All new service modules go in `backend/windmill-native-triggers/src/`.
-
----
-
-## Core Concepts
-
-### The `External` Trait
-
-Every native trigger service implements the `External` trait defined in `lib.rs`:
-
-```rust
-#[async_trait]
-pub trait External: Send + Sync + 'static {
- // Associated types:
- type ServiceConfig: Debug + DeserializeOwned + Serialize + Send + Sync;
- type TriggerData: Debug + Serialize + Send + Sync;
- type OAuthData: DeserializeOwned + Serialize + Clone + Send + Sync;
- type CreateResponse: DeserializeOwned + Send + Sync;
-
- // Constants:
- const SUPPORT_WEBHOOK: bool;
- const SERVICE_NAME: ServiceName;
- const DISPLAY_NAME: &'static str;
- const TOKEN_ENDPOINT: &'static str;
- const REFRESH_ENDPOINT: &'static str;
- const AUTH_ENDPOINT: &'static str;
-
- // Required methods:
- async fn create(&self, w_id, oauth_data, webhook_token, data, db, tx) -> Result;
- async fn update(&self, w_id, oauth_data, external_id, webhook_token, data, db, tx) -> Result;
- async fn get(&self, w_id, oauth_data, external_id, db, tx) -> Result;
- async fn delete(&self, w_id, oauth_data, external_id, db, tx) -> Result<()>;
- async fn exists(&self, w_id, oauth_data, external_id, db, tx) -> Result;
- async fn maintain_triggers(&self, db, workspace_id, triggers, oauth_data, synced, errors);
- fn external_id_and_metadata_from_response(&self, resp) -> (String, Option);
-
- // Methods with defaults:
- async fn prepare_webhook(&self, db, w_id, headers, body, script_path, is_flow) -> Result;
- fn service_config_from_create_response(&self, data, resp) -> Option;
- fn additional_routes(&self) -> axum::Router;
- async fn http_client_request(&self, url, method, workspace_id, tx, db, headers, body) -> Result;
-}
-```
-
-Key design points:
-- **`update()` returns `serde_json::Value`** - the resolved service_config to store. Each service is responsible for building the final config.
-- **`maintain_triggers()`** - periodic background maintenance. Each service implements its own strategy (Nextcloud: reconcile with external state; Google: renew expiring channels).
-- **No `list_all()` in the trait** - services that need it (Nextcloud) implement it privately; services that don't (Google) use different maintenance strategies.
-- **No `get_external_id_from_trigger_data()` or `extract_service_config_from_trigger_data()`** - removed in favor of the `maintain_triggers` pattern.
-
-### Create Lifecycle: Two Paths
-
-The `create_native_trigger` handler in `handler.rs` supports two creation flows, controlled by `service_config_from_create_response()`:
-
-**Path A: Short (Google pattern)** - `service_config_from_create_response()` returns `Some(config)`:
-1. `create()` registers on external service
-2. `external_id_and_metadata_from_response()` extracts the ID
-3. `service_config_from_create_response()` builds the config directly from input data + response metadata
-4. Stores trigger in DB -- done, no extra round-trip
-
-Use this when the external_id is known before the create call (e.g., Google generates the channel_id as a UUID upfront and includes it in the webhook URL).
-
-**Path B: Long (Nextcloud pattern)** - `service_config_from_create_response()` returns `None` (default):
-1. `create()` registers on external service (webhook URL has no external_id yet)
-2. `external_id_and_metadata_from_response()` extracts the ID
-3. `update()` is called to fix the webhook URL with the now-known external_id
-4. `update()` returns the resolved service_config
-5. Stores trigger in DB
-
-Use this when the external_id is assigned by the remote service and the webhook URL needs to be corrected after creation.
-
-### OAuth Token Storage (Three-Table Pattern)
-
-OAuth tokens are stored across three tables, NOT in `workspace_integrations.oauth_data` directly:
-
-| Table | What's Stored |
-|-------|---------------|
-| `workspace_integrations` | `oauth_data` JSON with `base_url`, `client_id`, `client_secret`, `instance_shared` flag; `resource_path` pointing to the variable |
-| `variable` | Encrypted `access_token` (at the path stored in `resource_path`), linked to `account` via `account` column |
-| `account` | `refresh_token`, keyed by `workspace_id` + `client` (service name) + `is_workspace_integration = true` |
-
-The `decrypt_oauth_data()` function in `lib.rs` assembles these into a unified struct:
-```rust
-pub struct OAuthConfig {
- pub base_url: String,
- pub access_token: String, // decrypted from variable
- pub refresh_token: Option, // from account table
- pub client_id: String, // from oauth_data or instance settings
- pub client_secret: String, // from oauth_data or instance settings
-}
-```
-
-Instance-level sharing: when `oauth_data.instance_shared == true`, `client_id` and `client_secret` are read from global settings instead of workspace_integrations.
-
-### URL Resolution
-
-The `resolve_endpoint()` helper handles both absolute and relative OAuth URLs:
-
-```rust
-pub fn resolve_endpoint(base_url: &str, endpoint: &str) -> String {
- if endpoint.starts_with("http://") || endpoint.starts_with("https://") {
- endpoint.to_string() // Google: absolute URLs
- } else {
- format!("{}{}", base_url, endpoint) // Nextcloud: relative paths
- }
-}
-```
-
-### ServiceName Methods
-
-`ServiceName` is the central registry enum. Each variant must implement these match arms:
-
-| Method | Purpose |
-|--------|---------|
-| `as_str()` | Lowercase identifier (e.g., `"google"`) |
-| `as_trigger_kind()` | Maps to `TriggerKind` enum |
-| `as_job_trigger_kind()` | Maps to `JobTriggerKind` enum |
-| `token_endpoint()` | OAuth token endpoint (relative or absolute) |
-| `auth_endpoint()` | OAuth authorization endpoint |
-| `oauth_scopes()` | Space-separated OAuth scopes |
-| `resource_type()` | Resource type for token storage (e.g., `"gworkspace"`) |
-| `extra_auth_params()` | Extra OAuth params (e.g., Google needs `access_type=offline`, `prompt=consent`) |
-| `integration_service()` | Maps to the workspace integration service (usually `*self`) |
-| `TryFrom` | Parse from string |
-| `Display` | Delegates to `as_str()` |
-
----
-
-## Step-by-Step Implementation Guide
-
-### Step 1: Database Migration
-
-Create a new migration file: `backend/migrations/YYYYMMDDHHMMSS_newservice_trigger.up.sql`
-
-```sql
--- Add the service to the native_trigger_service enum
-ALTER TYPE native_trigger_service ADD VALUE IF NOT EXISTS 'newservice';
-
--- Add to TRIGGER_KIND enum (used for trigger tracking)
-ALTER TYPE TRIGGER_KIND ADD VALUE IF NOT EXISTS 'newservice';
-
--- Add to job_trigger_kind enum (used for job tracking)
-ALTER TYPE job_trigger_kind ADD VALUE IF NOT EXISTS 'newservice';
-```
-
-Also create the corresponding down migration.
-
-### Step 2: Update windmill-common Enums
-
-#### `backend/windmill-common/src/triggers.rs`
-
-Add variant to `TriggerKind` enum, and update `to_key()` and `fmt()` implementations.
-
-#### `backend/windmill-common/src/jobs.rs`
-
-Add variant to `JobTriggerKind` enum and update the `Display` implementation.
-
-### Step 3: Backend Service Module
-
-Create a new directory: `backend/windmill-native-triggers/src/newservice/`
-
-#### `mod.rs` - Type Definitions
-
-```rust
-use serde::{Deserialize, Serialize};
-
-pub mod external;
-// pub mod routes; // Only if you need additional service-specific routes
-
-/// OAuth data deserialized from the three-table pattern.
-/// The actual structure is built by decrypt_oauth_data() from variable + account + workspace_integrations.
-#[derive(Debug, Clone, Deserialize, Serialize)]
-pub struct NewServiceOAuthData {
- pub base_url: String, // from workspace_integrations.oauth_data
- pub access_token: String, // decrypted from variable table
- pub refresh_token: Option, // from account table
- // Note: client_id and client_secret are in OAuthConfig, not here
- // unless the service needs them at runtime for API calls
-}
-
-/// Configuration provided by user when creating/updating a trigger.
-/// Stored as JSON in native_trigger.service_config.
-#[derive(Debug, Clone, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct NewServiceConfig {
- // Service-specific configuration fields
- pub folder_path: String,
- pub file_filter: Option,
-}
-
-/// Data retrieved from the external service about a trigger.
-/// Returned by the get() method and shown in the UI.
-#[derive(Debug, Clone, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct NewServiceTriggerData {
- pub folder_path: String,
- pub file_filter: Option,
- // Fields that shouldn't affect service_config comparison should use #[serde(skip_serializing)]
-}
-
-/// Response from external service when creating a trigger/webhook.
-#[derive(Debug, Deserialize)]
-pub struct CreateTriggerResponse {
- pub id: String,
-}
-
-/// Handler struct (stateless, used for routing)
-#[derive(Copy, Clone)]
-pub struct NewService;
-```
-
-#### `external.rs` - External Trait Implementation
-
-```rust
-use async_trait::async_trait;
-use reqwest::Method;
-use sqlx::PgConnection;
-use std::collections::HashMap;
-use windmill_common::{
- error::{Error, Result},
- BASE_URL, DB,
-};
-
-use crate::{
- generate_webhook_service_url, External, NativeTrigger, NativeTriggerData, ServiceName,
- sync::{SyncError, TriggerSyncInfo},
-};
-use super::{NewService, NewServiceConfig, NewServiceOAuthData, NewServiceTriggerData, CreateTriggerResponse};
-
-#[async_trait]
-impl External for NewService {
- type ServiceConfig = NewServiceConfig;
- type TriggerData = NewServiceTriggerData;
- type OAuthData = NewServiceOAuthData;
- type CreateResponse = CreateTriggerResponse;
-
- const SERVICE_NAME: ServiceName = ServiceName::NewService;
- const DISPLAY_NAME: &'static str = "New Service";
- const SUPPORT_WEBHOOK: bool = true;
- const TOKEN_ENDPOINT: &'static str = "/oauth/token";
- const REFRESH_ENDPOINT: &'static str = "/oauth/token";
- const AUTH_ENDPOINT: &'static str = "/oauth/authorize";
-
- async fn create(
- &self,
- w_id: &str,
- oauth_data: &Self::OAuthData,
- webhook_token: &str,
- data: &NativeTriggerData,
- db: &DB,
- tx: &mut PgConnection,
- ) -> Result {
- let base_url = &*BASE_URL.read().await;
-
- // external_id is None during create (we get it from the response)
- let webhook_url = generate_webhook_service_url(
- base_url, w_id, &data.script_path, data.is_flow,
- None, Self::SERVICE_NAME, webhook_token,
- );
-
- let url = format!("{}/api/webhooks/create", oauth_data.base_url);
- let payload = serde_json::json!({
- "callback_url": webhook_url,
- "folder_path": data.service_config.folder_path,
- });
-
- let response: CreateTriggerResponse = self
- .http_client_request(&url, Method::POST, w_id, tx, db, None, Some(&payload))
- .await?;
-
- Ok(response)
- }
-
- /// Update returns the resolved service_config as JSON.
- /// For services using the update+get pattern, call self.get() and serialize.
- async fn update(
- &self,
- w_id: &str,
- oauth_data: &Self::OAuthData,
- external_id: &str,
- webhook_token: &str,
- data: &NativeTriggerData,
- db: &DB,
- tx: &mut PgConnection,
- ) -> Result {
- let base_url = &*BASE_URL.read().await;
-
- let webhook_url = generate_webhook_service_url(
- base_url, w_id, &data.script_path, data.is_flow,
- Some(external_id), Self::SERVICE_NAME, webhook_token,
- );
-
- let url = format!("{}/api/webhooks/{}", oauth_data.base_url, external_id);
- let payload = serde_json::json!({
- "callback_url": webhook_url,
- "folder_path": data.service_config.folder_path,
- });
-
- let _: serde_json::Value = self
- .http_client_request(&url, Method::PUT, w_id, tx, db, None, Some(&payload))
- .await?;
-
- // Fetch back the updated state to get the resolved config
- let trigger_data = self.get(w_id, oauth_data, external_id, db, tx).await?;
- serde_json::to_value(&trigger_data)
- .map_err(|e| Error::InternalErr(format!("Failed to serialize trigger data: {}", e)))
- }
-
- async fn get(
- &self,
- w_id: &str,
- oauth_data: &Self::OAuthData,
- external_id: &str,
- db: &DB,
- tx: &mut PgConnection,
- ) -> Result {
- let url = format!("{}/api/webhooks/{}", oauth_data.base_url, external_id);
- self.http_client_request::<_, ()>(&url, Method::GET, w_id, tx, db, None, None).await
- }
-
- async fn delete(
- &self,
- w_id: &str,
- oauth_data: &Self::OAuthData,
- external_id: &str,
- db: &DB,
- tx: &mut PgConnection,
- ) -> Result<()> {
- let url = format!("{}/api/webhooks/{}", oauth_data.base_url, external_id);
- let _: serde_json::Value = self
- .http_client_request::<_, ()>(&url, Method::DELETE, w_id, tx, db, None, None)
- .await
- .or_else(|e| match &e {
- Error::InternalErr(msg) if msg.contains("404") => Ok(serde_json::Value::Null),
- _ => Err(e),
- })?;
- Ok(())
- }
-
- async fn exists(
- &self,
- w_id: &str,
- oauth_data: &Self::OAuthData,
- external_id: &str,
- db: &DB,
- tx: &mut PgConnection,
- ) -> Result {
- match self.get(w_id, oauth_data, external_id, db, tx).await {
- Ok(_) => Ok(true),
- Err(Error::NotFound(_)) => Ok(false),
- Err(e) => Err(e),
- }
- }
-
- /// Background maintenance. Choose the right pattern for your service:
- /// - For services with queryable external state: use reconcile_with_external_state()
- /// - For channel-based services with expiration: implement renewal logic
- async fn maintain_triggers(
- &self,
- db: &DB,
- workspace_id: &str,
- triggers: &[NativeTrigger],
- oauth_data: &Self::OAuthData,
- synced: &mut Vec,
- errors: &mut Vec,
- ) {
- // Option A: Reconcile with external state (Nextcloud pattern)
- // Fetch all triggers from external service and compare with DB
- let external_triggers = match self.list_all(workspace_id, oauth_data, db).await {
- Ok(triggers) => triggers,
- Err(e) => {
- errors.push(SyncError {
- resource_path: format!("workspace:{}", workspace_id),
- error_message: format!("Failed to list triggers: {}", e),
- error_type: "api_error".to_string(),
- });
- return;
- }
- };
-
- // Convert to (external_id, config_json) pairs
- let external_pairs: Vec<(String, serde_json::Value)> = external_triggers
- .into_iter()
- .map(|t| (t.id.clone(), serde_json::to_value(&t).unwrap_or_default()))
- .collect();
-
- crate::sync::reconcile_with_external_state(
- db, workspace_id, Self::SERVICE_NAME, triggers, &external_pairs, synced, errors,
- ).await;
- }
-
- fn external_id_and_metadata_from_response(
- &self,
- resp: &Self::CreateResponse,
- ) -> (String, Option) {
- (resp.id.clone(), None)
- }
-
- // service_config_from_create_response: NOT overridden (returns None).
- // This means the handler uses the update+get pattern after create.
- // Override and return Some(...) to skip the update+get cycle (Google pattern).
-}
-
-impl NewService {
- /// Private helper to list all triggers from the external service.
- async fn list_all(
- &self,
- w_id: &str,
- oauth_data: &::OAuthData,
- db: &DB,
- ) -> Result::TriggerData>> {
- // Implementation depends on the external service's API
- todo!()
- }
-}
-```
-
-### Step 4: Update lib.rs Registry
-
-In `backend/windmill-native-triggers/src/lib.rs`:
-
-```rust
-// Service modules - add new services here:
-#[cfg(feature = "native_trigger")]
-pub mod newservice; // <-- Add this
-
-// ServiceName enum - add variant:
-pub enum ServiceName {
- Nextcloud,
- Google,
- NewService, // <-- Add this
-}
-
-// Then add match arms in ALL ServiceName methods:
-// as_str(), as_trigger_kind(), as_job_trigger_kind(), token_endpoint(),
-// auth_endpoint(), oauth_scopes(), resource_type(), extra_auth_params(),
-// integration_service(), TryFrom, Display
-```
-
-### Step 5: Update handler.rs Routes
-
-In `backend/windmill-native-triggers/src/handler.rs`:
-
-```rust
-pub fn generate_native_trigger_routers() -> Router {
- // ...
- #[cfg(feature = "native_trigger")]
- {
- use crate::newservice::NewService;
- return router
- .nest("/nextcloud", service_routes(NextCloud))
- .nest("/google", service_routes(Google))
- .nest("/newservice", service_routes(NewService)); // <-- Add this
- }
- // ...
-}
-```
-
-### Step 6: Update sync.rs
-
-In `backend/windmill-native-triggers/src/sync.rs`:
-
-```rust
-pub async fn sync_all_triggers(db: &DB) -> Result {
- // ...
- #[cfg(feature = "native_trigger")]
- {
- use crate::newservice::NewService;
-
- // ... existing service syncs ...
-
- // New service sync
- let (service_name, result) = sync_service_triggers(db, NewService).await;
- total_synced += result.synced_triggers.len();
- total_errors += result.errors.len();
- service_results.insert(service_name, result);
- }
- // ...
-}
-```
-
-### Step 7: Frontend Service Registry
-
-In `frontend/src/lib/components/triggers/native/utils.ts`:
-
-Add to `NATIVE_TRIGGER_SERVICES`, `getTriggerIconName()`, and `getServiceIcon()`.
-
-### Step 8: Frontend Trigger Form Component
-
-Create: `frontend/src/lib/components/triggers/native/services/newservice/NewServiceTriggerForm.svelte`
-
-### Step 9: Frontend Icon Component
-
-Create: `frontend/src/lib/components/icons/NewServiceIcon.svelte`
-
-### Step 10: Update NativeTriggerEditor
-
-Check `frontend/src/lib/components/triggers/native/NativeTriggerEditor.svelte` to ensure it dynamically loads form components based on service name.
-
-### Step 11: Workspace Integration UI
-
-Add your service to the `supportedServices` map in `frontend/src/lib/components/workspaceSettings/WorkspaceIntegrations.svelte`:
-
-```typescript
-const supportedServices: Record = {
- // ... existing services ...
- newservice: {
- name: 'newservice',
- displayName: 'New Service',
- description: 'Connect to New Service for triggers',
- icon: NewServiceIcon,
- docsUrl: 'https://www.windmill.dev/docs/integrations/newservice',
- requiresBaseUrl: false, // false for cloud services, true for self-hosted
- setupInstructions: [
- 'Step 1: Create an OAuth app on the service',
- 'Step 2: Configure the redirect URI shown below',
- 'Step 3: Enter the client credentials below'
- ]
- }
-}
-```
-
-### Step 12: Update `frontend/src/lib/components/triggers/utils.ts`
-
-Update ALL of these maps/functions:
-1. `triggerIconMap` - import and add icon
-2. `triggerDisplayNamesMap` - add display name
-3. `triggerTypeOrder` in `sortTriggers()` - add type
-4. `getLightConfig()` - add case for your service
-5. `getTriggerLabel()` - add case for your service
-6. `jobTriggerKinds` - add to array
-7. `countPropertyMap` - add count property
-8. `triggerSaveFunctions` - add save function
-
-### Step 13: Update TriggersBadge Component
-
-In `frontend/src/lib/components/graph/renderers/triggers/TriggersBadge.svelte`:
-
-1. Import the icon
-2. Add to `baseConfig` with `countKey` (the dynamic `availableNativeServices` loop does NOT set `countKey`)
-3. Add to the `allTypes` array
-
-### Step 14: Update TriggersWrapper.svelte
-
-In `frontend/src/lib/components/triggers/TriggersWrapper.svelte`:
-
-Add a `{:else if selectedTrigger.type === 'yourservice'}` case that renders `` with the same props pattern as the existing native trigger cases (e.g., `nextcloud`).
-
-### Step 15: Update AddTriggersButton.svelte
-
-In `frontend/src/lib/components/triggers/AddTriggersButton.svelte`:
-
-1. Add `yourserviceAvailable` state variable
-2. Add `setYourserviceState()` async function using `isServiceAvailable('yourservice', $workspaceStore!)`
-3. Call it at module level
-4. Add a dropdown entry to `addTriggerItems` with `hidden: !yourserviceAvailable`
-
-### Step 16: Update TriggersEditor.svelte Delete Handling
-
-In `frontend/src/lib/components/triggers/TriggersEditor.svelte`:
-
-Add your service to the `nativeTriggerServices` map in `deleteDeployedTrigger()`. Native triggers use `NativeTriggerService.deleteNativeTrigger({ workspace, serviceName, externalId })` instead of the standard `path`-based delete.
-
-### Step 17: Update `getUsedTriggers` for Sidebar Visibility
-
-The sidebar (`frontend/src/lib/components/sidebar/SidebarContent.svelte`) shows native-trigger links only if `$usedTriggerKinds` includes the service — without this, your trigger page will never appear in the nav bar even when triggers exist.
-
-1. **Backend** — add `{service}_used: bool` to the `UsedTriggers` struct and SELECT in `backend/windmill-api-workspaces/src/workspaces.rs::get_used_triggers()`:
- ```rust
- EXISTS(SELECT 1 FROM native_trigger WHERE workspace_id = $1 AND service_name = '{service}'::native_trigger_service) AS "{service}_used!"
- ```
-2. **OpenAPI** — add `{service}_used: boolean` to the response schema for `GET /w/{workspace}/workspaces/used_triggers` (under both `properties` and `required`).
-3. **Layout** — in `frontend/src/routes/(root)/(logged)/+layout.svelte::loadUsedTriggerKinds()`, destructure `{service}_used` and push `'{service}'` to `usedKinds`.
-
-### Step 18: Update OpenAPI Spec and Regenerate Types
-
-Add to `JobTriggerKind` enum in `backend/windmill-api/openapi.yaml`, then:
-
-```bash
-cd frontend && npm run generate-backend-client
-```
-
----
-
-## Special Patterns
-
-### Unified Service with `trigger_type` (Google Pattern)
-
-When a single service handles multiple trigger types (e.g., Google Drive + Calendar share OAuth and API patterns), use a single `ServiceName` variant with a discriminator field:
-
-```rust
-pub enum GoogleTriggerType { Drive, Calendar }
-
-pub struct GoogleServiceConfig {
- pub trigger_type: GoogleTriggerType,
- // Drive-specific fields (only used when trigger_type = Drive)
- pub resource_id: Option,
- pub resource_name: Option,
- // Calendar-specific fields (only used when trigger_type = Calendar)
- pub calendar_id: Option,
- pub calendar_name: Option,
- // Metadata set after creation
- pub google_resource_id: Option,
- pub expiration: Option,
-}
-```
-
-Branch in trait methods based on `trigger_type`. Frontend uses a `ToggleButtonGroup` to switch between types. This keeps the codebase simpler (one service, one OAuth flow, one set of routes).
-
-See `backend/windmill-native-triggers/src/google/` for the reference implementation.
-
-### Skipping update+get After Create (Google Pattern)
-
-Override `service_config_from_create_response()` to return `Some(config)` when the external_id is known before the create call:
-
-```rust
-fn service_config_from_create_response(
- &self,
- data: &NativeTriggerData,
- resp: &Self::CreateResponse,
-) -> Option {
- // Clone input config, add metadata from response
- let mut config = data.service_config.clone();
- config.google_resource_id = Some(resp.resource_id.clone());
- config.expiration = Some(resp.expiration.clone());
- Some(serde_json::to_value(&config).unwrap())
-}
-```
-
-### Services with Absolute OAuth Endpoints (Google)
-
-Unlike self-hosted services where OAuth endpoints are relative paths appended to `base_url`, services like Google have absolute URLs:
-
-```rust
-// Nextcloud: relative paths
-ServiceName::Nextcloud => "/apps/oauth2/api/v1/token",
-// Google: absolute URLs
-ServiceName::Google => "https://oauth2.googleapis.com/token",
-```
-
-The `resolve_endpoint()` function handles both. For services with absolute endpoints:
-- `base_url` can be empty
-- `requiresBaseUrl: false` in the frontend workspace integration config
-- Add `extra_auth_params()` if needed (Google requires `access_type=offline` and `prompt=consent`)
-
-### Channel-Based Push Notifications with Renewal (Google Pattern)
-
-For services using expiring watch channels instead of persistent webhooks:
-
-1. Store expiration in `service_config` (as part of `ServiceConfig`)
-2. In `maintain_triggers()`, implement renewal logic instead of using `reconcile_with_external_state()`:
- ```rust
- async fn maintain_triggers(&self, db, workspace_id, triggers, oauth_data, synced, errors) {
- for trigger in triggers {
- if should_renew_channel(trigger) {
- self.renew_channel(db, trigger, oauth_data).await;
- }
- }
- }
- ```
-3. Renewal: best-effort stop old channel, create new one with same external_id, update service_config with new expiration
-4. Google example: Drive channels expire in 24h (renew when <1h left), Calendar channels expire in 7 days (renew when <1 day left)
-
-### reconcile_with_external_state (Nextcloud Pattern)
-
-The reusable function in `sync.rs` compares external triggers with DB state:
-- Triggers missing externally: sets error "Trigger no longer exists on external service"
-- Triggers present externally: clears errors, updates service_config if it differs
-
-Usage in `maintain_triggers()`:
-```rust
-let external_pairs: Vec<(String, serde_json::Value)> = /* fetch from external */;
-crate::sync::reconcile_with_external_state(
- db, workspace_id, Self::SERVICE_NAME, triggers, &external_pairs, synced, errors,
-).await;
-```
-
-### Webhook Payload Processing
-
-Override `prepare_webhook()` to parse service-specific payloads into script/flow args:
-
-```rust
-async fn prepare_webhook(&self, db, w_id, headers, body, script_path, is_flow) -> Result {
- let mut args = HashMap::new();
- args.insert("event_type".to_string(), Box::new(headers.get("x-event-type").cloned()) as _);
- args.insert("payload".to_string(), Box::new(serde_json::from_str::(&body)?) as _);
- Ok(PushArgsOwned { extra: None, args })
-}
-```
-
-Then register in `prepare_native_trigger_args()` in `lib.rs`:
-```rust
-pub async fn prepare_native_trigger_args(service_name, db, w_id, headers, body) -> Result> {
- match service_name {
- ServiceName::Google => { /* ... */ Ok(Some(args)) }
- ServiceName::NewService => { /* ... */ Ok(Some(args)) }
- ServiceName::Nextcloud => Ok(None), // Uses default body parsing
- }
-}
-```
-
-### Instance-Level OAuth Credentials
-
-When `workspace_integrations.oauth_data.instance_shared == true`, `decrypt_oauth_data()` reads `client_id` and `client_secret` from instance-level global settings instead of workspace-level. This allows admins to share OAuth app credentials across workspaces.
-
-The frontend handles this via the `generate_instance_connect_url` endpoint in `workspace_integrations.rs`.
-
----
-
-## Testing Checklist
-
-- [ ] Database migration runs successfully
-- [ ] `cargo check -p windmill-native-triggers --features native_trigger` passes
-- [ ] `npx svelte-check --threshold error` passes (in frontend/)
-- [ ] Service appears in workspace integrations list
-- [ ] OAuth flow completes successfully
-- [ ] Can create a new trigger
-- [ ] Can view trigger details
-- [ ] Can update trigger configuration
-- [ ] Can delete trigger
-- [ ] Webhook receives and processes payloads
-- [ ] Background sync works correctly (reconciliation or channel renewal)
-- [ ] Error handling works (expired tokens, service unavailable)
-
----
-
-## Reference Implementations
-
-### Nextcloud (Self-Hosted, Update+Get Pattern)
-
-| File | Purpose |
-|------|---------|
-| `nextcloud/mod.rs` | Types: NextCloudOAuthData, NextcloudServiceConfig, NextCloudTriggerData |
-| `nextcloud/external.rs` | External trait: uses update+get pattern, reconcile_with_external_state for sync |
-| `nextcloud/routes.rs` | Additional route: `GET /events` |
-
-Key patterns: relative OAuth endpoints, base_url required, list_all + reconcile for sync, update returns JSON from get().
-
-### Google (Cloud, Unified Service, Short Create)
-
-| File | Purpose |
-|------|---------|
-| `google/mod.rs` | Types: GoogleServiceConfig with trigger_type discriminator, GoogleTriggerType enum |
-| `google/external.rs` | External trait: overrides service_config_from_create_response, channel renewal for sync |
-| `google/routes.rs` | Additional routes: `GET /calendars`, `GET /drive/files`, `GET /drive/shared_drives` |
-
-Key patterns: absolute OAuth endpoints, empty base_url, trigger_type for Drive/Calendar, expiring watch channels with renewal, service_config_from_create_response skips update+get, get() reconstructs data from stored service_config (no external "get channel" API).
diff --git a/.claude/skills/native-trigger/SKILL.md b/.claude/skills/native-trigger/SKILL.md
new file mode 120000
index 0000000000..18548efdba
--- /dev/null
+++ b/.claude/skills/native-trigger/SKILL.md
@@ -0,0 +1 @@
+../../../.agents/skills/native-trigger/SKILL.md
\ No newline at end of file
diff --git a/.claude/skills/pr/SKILL.md b/.claude/skills/pr/SKILL.md
deleted file mode 100644
index 2c7bd691ca..0000000000
--- a/.claude/skills/pr/SKILL.md
+++ /dev/null
@@ -1,111 +0,0 @@
----
-name: pr
-user_invocable: true
-description: Open a draft pull request on GitHub. MUST use when you want to create/open a PR.
----
-
-# Pull Request Skill
-
-Create a draft pull request with a clear title and explicit description of changes.
-
-## Instructions
-
-1. **Analyze branch changes**: Understand all commits since diverging from main
-2. **Push to remote**: Ensure all commits are pushed
-3. **Create draft PR**: Always open as draft for review before merging
-
-## PR Title Format
-
-Follow conventional commit format for the PR title:
-```
-:
-```
-
-### Types
-- `feat`: New feature or capability
-- `fix`: Bug fix
-- `refactor`: Code restructuring
-- `docs`: Documentation changes
-- `chore`: Maintenance tasks
-- `perf`: Performance improvements
-
-### Title Rules
-- Keep under 70 characters
-- Use lowercase, imperative mood
-- No period at the end
-- If `*_ee.rs` files were modified, prefix with `[ee]`: `[ee] : `
-
-## PR Body Format
-
-The body MUST be explicit about what changed. Structure:
-
-```markdown
-## Summary
-
-
-## Changes
--
--
--
-
-## Test plan
-- [ ]
-- [ ]
-
----
-Generated with [Claude Code](https://claude.com/claude-code)
-```
-
-## Execution Steps
-
-1. Run `git status` to check for uncommitted changes
-2. Run `git log main..HEAD --oneline` to see all commits in this branch
-3. Run `git diff main...HEAD` to see the full diff against main
-4. **Run `/local-review`** before creating the PR. If issues are found, fix them and commit before proceeding. Do not skip this step.
-5. Check if remote branch exists and is up to date:
- ```bash
- git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null || echo "no upstream"
- ```
-6. Push to remote if needed: `git push -u origin HEAD`
-7. Create draft PR using gh CLI:
- ```bash
- gh pr create --draft --title ": " --body "$(cat <<'EOF'
- ## Summary
-
-
- ## Changes
- -
- -
-
- ## Test plan
- - [ ]
- - [ ]
-
- ---
- Generated with [Claude Code](https://claude.com/claude-code)
- EOF
- )"
- ```
-8. Return the PR URL to the user
-
-## EE Companion PR (when `*_ee.rs` files were modified)
-
-The `*_ee.rs` files in the windmill repo are **symlinks** to `windmill-ee-private` — changes won't appear in `git diff` of the windmill repo. Instead, check the EE repo for uncommitted or unpushed changes.
-
-Follow the full EE PR workflow in `docs/enterprise.md`. The key PR-specific details:
-
-1. Find the EE repo/worktree: see "Finding the EE Repo" in `docs/enterprise.md`
-2. Check for changes: `git -C status --short`
- - If there are no changes in the EE repo, skip this entire section
-3. Follow steps 1–5 from the "EE PR Workflow" in `docs/enterprise.md`
-4. Create the companion PR (title does NOT get the `[ee]` prefix):
- ```bash
- gh pr create --draft --repo windmill-labs/windmill-ee-private --title ": " --body "$(cat <<'EOF'
- Companion PR for windmill-labs/windmill#
-
- ---
- Generated with [Claude Code](https://claude.com/claude-code)
- EOF
- )"
- ```
-5. Commit `ee-repo-ref.txt` and push the updated windmill branch
diff --git a/.claude/skills/pr/SKILL.md b/.claude/skills/pr/SKILL.md
new file mode 120000
index 0000000000..9458ad7097
--- /dev/null
+++ b/.claude/skills/pr/SKILL.md
@@ -0,0 +1 @@
+../../../.agents/skills/pr/SKILL.md
\ No newline at end of file
diff --git a/.claude/skills/refine/SKILL.md b/.claude/skills/refine/SKILL.md
deleted file mode 100644
index aaf747cd29..0000000000
--- a/.claude/skills/refine/SKILL.md
+++ /dev/null
@@ -1,39 +0,0 @@
----
-name: refine
-user_invocable: true
-description: End-of-session reflection. Reviews friction encountered during the session and proposes updates to docs/ to capture lessons learned.
----
-
-# Refine Skill
-
-Reflect on the current session and update documentation with lessons learned.
-
-## Instructions
-
-1. **Identify friction**: Review what happened in this session:
- - Run `git diff main...HEAD --stat` to see what files were touched
- - Think about: what was slow, what failed, what required multiple attempts, what information was missing or hard to find
-
-2. **Read current docs**: Read the docs that were relevant to this session:
- - `docs/validation.md`
- - `docs/enterprise.md`
- - `docs/autonomous-mode.md`
- - Any skills that were invoked
-
-3. **Propose updates**: For each piece of friction, decide if it warrants a doc update:
- - **Missing knowledge**: Information you had to discover that should be documented
- - **Wrong guidance**: Instructions that led you astray
- - **Missing validation rule**: A check that should be in the validation matrix
- - **New pattern**: A codebase pattern worth capturing for next time
-
-4. **Apply updates**: Edit the relevant `docs/` files. Keep changes minimal and specific — add only what would have saved time this session.
-
-5. **Report**: Summarize what was added/changed and why.
-
-## Rules
-
-- Only add knowledge confirmed by this session — no speculative additions
-- Keep docs concise — add a line or two, not a paragraph
-- If a whole new doc is needed, create it in `docs/` and add a pointer in `CLAUDE.md`
-- Don't update skills unless a coding pattern was genuinely wrong
-- Don't add things Claude already knows — only Windmill-specific knowledge
diff --git a/.claude/skills/refine/SKILL.md b/.claude/skills/refine/SKILL.md
new file mode 120000
index 0000000000..39580df5d0
--- /dev/null
+++ b/.claude/skills/refine/SKILL.md
@@ -0,0 +1 @@
+../../../.agents/skills/refine/SKILL.md
\ No newline at end of file
diff --git a/.claude/skills/rust-backend/SKILL.md b/.claude/skills/rust-backend/SKILL.md
deleted file mode 100644
index f0c52002bc..0000000000
--- a/.claude/skills/rust-backend/SKILL.md
+++ /dev/null
@@ -1,107 +0,0 @@
----
-name: rust-backend
-description: Rust coding guidelines for the Windmill backend. MUST use when writing or modifying Rust code in the backend directory.
----
-
-# Windmill Rust Patterns
-
-Apply these Windmill-specific patterns when writing Rust code in `backend/`.
-
-## Error Handling
-
-Use `Error` from `windmill_common::error`. Return `Result` or `JsonResult`:
-
-```rust
-use windmill_common::error::{Error, Result};
-
-pub async fn get_job(db: &DB, id: Uuid) -> Result {
- sqlx::query_as!(Job, "SELECT id, workspace_id FROM v2_job WHERE id = $1", id)
- .fetch_optional(db)
- .await?
- .ok_or_else(|| Error::NotFound("job not found".to_string()))?;
-}
-```
-
-Never panic in library code. Reserve `.unwrap()` for compile-time guarantees.
-
-## SQLx Patterns
-
-**Never use `SELECT *`** — always list columns explicitly. Critical for backwards compatibility when workers lag behind API version:
-
-```rust
-// Correct
-sqlx::query_as!(Job, "SELECT id, workspace_id, path FROM v2_job WHERE id = $1", id)
-
-// Wrong — breaks when columns are added
-sqlx::query_as!(Job, "SELECT * FROM v2_job WHERE id = $1", id)
-```
-
-Use batch operations to avoid N+1:
-
-```rust
-// Preferred — single query with IN clause
-sqlx::query!("SELECT ... WHERE id = ANY($1)", &ids[..]).fetch_all(db).await?
-```
-
-Use transactions for multi-step operations. Parameterize all queries.
-
-## JSON Handling
-
-Prefer `Box` over `serde_json::Value` when storing/passing JSON without inspection:
-
-```rust
-pub struct Job {
- pub args: Option>,
-}
-```
-
-Only use `serde_json::Value` when you need to inspect or modify the JSON.
-
-## Serde Optimizations
-
-```rust
-#[derive(Serialize, Deserialize)]
-pub struct Job {
- #[serde(skip_serializing_if = "Option::is_none")]
- pub parent_job: Option,
- #[serde(skip_serializing_if = "Vec::is_empty")]
- pub tags: Vec,
- #[serde(default)]
- pub priority: i32,
-}
-```
-
-## Async & Concurrency
-
-Never block the async runtime. Use `spawn_blocking` for CPU-intensive work:
-
-```rust
-let result = tokio::task::spawn_blocking(move || expensive_computation(&data)).await?;
-```
-
-**Mutex selection**: Prefer `std::sync::Mutex` (or `parking_lot::Mutex`) for data protection. Only use `tokio::sync::Mutex` when holding locks across `.await` points.
-
-Use `tokio::sync::mpsc` (bounded) for channels. Avoid `std::thread::sleep` in async contexts.
-
-## Module Structure & Visibility
-
-- Use `pub(crate)` instead of `pub` when possible
-- Place new code in the appropriate crate based on functionality
-- API endpoints go in `windmill-api/src/` organized by domain
-- Shared functionality goes in `windmill-common/src/`
-
-## Code Navigation
-
-Always use rust-analyzer LSP for go-to-definition, find-references, and type info. Do not guess at module paths.
-
-## Axum Handlers
-
-Destructure extractors directly in function signatures:
-
-```rust
-async fn process_job(
- Extension(db): Extension,
- Path((workspace, job_id)): Path<(String, Uuid)>,
- Query(pagination): Query,
-) -> Result> { ... }
-```
diff --git a/.claude/skills/rust-backend/SKILL.md b/.claude/skills/rust-backend/SKILL.md
new file mode 120000
index 0000000000..2500c55046
--- /dev/null
+++ b/.claude/skills/rust-backend/SKILL.md
@@ -0,0 +1 @@
+../../../.agents/skills/rust-backend/SKILL.md
\ No newline at end of file
diff --git a/.claude/skills/svelte-frontend/SKILL.md b/.claude/skills/svelte-frontend/SKILL.md
deleted file mode 100644
index 57cac70302..0000000000
--- a/.claude/skills/svelte-frontend/SKILL.md
+++ /dev/null
@@ -1,80 +0,0 @@
----
-name: svelte-frontend
-description: Svelte coding guidelines for the Windmill frontend. MUST use when writing or modifying code in the frontend directory.
----
-
-# Windmill Svelte Patterns
-
-Apply these Windmill-specific patterns when writing Svelte code in `frontend/`. For general Svelte 5 syntax (runes, snippets, event handling), use the Svelte MCP server.
-
-## Windmill UI Components (MUST use)
-
-Always use Windmill's design-system components. Never use raw HTML elements.
-
-### Buttons — ``
-
-```svelte
-
-
-Label
-
-```
-
-Props: `variant?: 'accent' | 'accent-secondary' | 'default' | 'subtle'`, `unifiedSize?: 'sm' | 'md' | 'lg'`, `startIcon?: { icon: SvelteComponent }`, `iconOnly?: boolean`, `disabled?: boolean`
-
-### Text inputs — ``
-
-```svelte
-
-
-
-```
-
-Props: `value?: string | number` (bindable), `placeholder?: string`, `disabled?: boolean`, `error?: string | boolean`, `size?: 'sm' | 'md' | 'lg'`
-
-### Selects — ``
-
-```svelte
-
-
-
-```
-
-Props: `items?: Array<{ label?: string; value: any }>`, `value` (bindable), `placeholder?: string`, `clearable?: boolean`, `size?: 'sm' | 'md' | 'lg'`
-
-### Icons — `lucide-svelte`
-
-Never write inline SVGs. Import from `lucide-svelte`:
-
-```svelte
-
-
-```
-
-## Form Components
-
-Form components (TextInput, Toggle, Select, etc.) should use the unified size system when placed together.
-
-## Styling
-
-- Use Tailwind CSS for all styling — no custom CSS
-- Use Windmill's theming classes for colors/surfaces (see `frontend/brand-guidelines.md`)
-- Read component props JSDoc before using them
-
-## Svelte MCP Server
-
-Use the Svelte MCP tools when working on Svelte code:
-
-1. **list-sections**: Call first to discover available docs
-2. **get-documentation**: Fetch relevant sections based on use_cases
-3. **svelte-autofixer**: MUST use on all Svelte code before finalizing — keep calling until no issues
-4. **playground-link**: Only after user confirms and code was NOT written to project files
diff --git a/.claude/skills/svelte-frontend/SKILL.md b/.claude/skills/svelte-frontend/SKILL.md
new file mode 120000
index 0000000000..a37bf39bd3
--- /dev/null
+++ b/.claude/skills/svelte-frontend/SKILL.md
@@ -0,0 +1 @@
+../../../.agents/skills/svelte-frontend/SKILL.md
\ No newline at end of file
diff --git a/.claude/skills/update-sqlx/SKILL.md b/.claude/skills/update-sqlx/SKILL.md
new file mode 120000
index 0000000000..5e75e2ceaa
--- /dev/null
+++ b/.claude/skills/update-sqlx/SKILL.md
@@ -0,0 +1 @@
+../../../.agents/skills/update-sqlx/SKILL.md
\ No newline at end of file
diff --git a/.github/codex/pr-review.prompt.md b/.github/codex/pr-review.prompt.md
index d3e6dfc4e8..fef52dba85 100644
--- a/.github/codex/pr-review.prompt.md
+++ b/.github/codex/pr-review.prompt.md
@@ -1,23 +1,5 @@
-You are reviewing a GitHub pull request for this repository.
+# Codex output format
-Review policy:
-- Read `CLAUDE.md` before reviewing code.
-- Only report issues you are confident are real and introduced by this pull request.
-- Focus on bugs, security problems, and clear `CLAUDE.md` violations.
-- Do not report style nits, speculative concerns, pre-existing issues, or problems that a normal linter/typechecker would obviously catch.
-- Keep the review high signal. If there is no clear issue, return no findings.
-
-Repository context:
-- Read `./.github/codex/pr-review-context.md` for the PR metadata and the exact diff commands to use.
-- Review only the changes introduced by this PR.
-- Read additional files only when the diff is not enough to validate a finding.
-- Do not modify any files.
-
-Output requirements:
-- Return a GitHub PR comment in markdown, not JSON.
-- Start with `## Codex Review`.
-- Give a short overall summary first.
-- If you found high-signal issues, list them in a short numbered list with file paths and line numbers when you know them confidently.
-- If you found no high-signal issues, say that explicitly.
-- End with a `### Reproduction instructions` section containing a short descriptive paragraph for a tester explaining how to navigate the app to observe the change. Do not make it a numbered list. If the diff is not enough to infer this safely, say that plainly.
-- Prefer at most 10 findings.
+- Read `./.github/codex/pr-review-context.md` for PR metadata and the diff commands.
+- Return a markdown PR comment starting with `## Codex Review`.
+- Tag each finding with a severity (P0 / P1 / P2), file path, and line number when known confidently.
diff --git a/.github/pi/pr-review.prompt.md b/.github/pi/pr-review.prompt.md
new file mode 100644
index 0000000000..92f128c6b1
--- /dev/null
+++ b/.github/pi/pr-review.prompt.md
@@ -0,0 +1,6 @@
+# Pi output format
+
+- Read `./.github/pi/pr-review-context.md` for PR metadata and the diff commands.
+- Return a markdown PR comment starting with `## Pi Review`.
+- Tag each finding with a severity (P0 / P1 / P2), file path, and line number when known confidently.
+- Output ONLY the final review markdown — no preamble, no thinking, no tool transcripts.
diff --git a/.github/workflows/backend-test-windows.yml b/.github/workflows/backend-test-windows.yml
index 2b76eda11f..f7c49654d1 100644
--- a/.github/workflows/backend-test-windows.yml
+++ b/.github/workflows/backend-test-windows.yml
@@ -98,6 +98,21 @@ jobs:
vcpkg.exe install openssl:x64-windows-static
vcpkg.exe integrate install
+ - name: Free disk space (post-vcpkg)
+ shell: pwsh
+ run: |
+ # vcpkg leaves multi-GB of buildtrees/downloads after installing openssl;
+ # we only need the installed/ dir for linking.
+ $vcpkgRoot = $env:VCPKG_INSTALLATION_ROOT
+ foreach ($sub in @("buildtrees", "downloads", "packages")) {
+ $path = Join-Path $vcpkgRoot $sub
+ if (Test-Path $path) {
+ Write-Host "Removing $path"
+ Remove-Item -Recurse -Force -ErrorAction SilentlyContinue $path
+ }
+ }
+ Get-PSDrive C | Select-Object Used,Free | Format-Table -AutoSize
+
- name: Get runtime paths
id: runtime-paths
shell: pwsh
@@ -119,6 +134,10 @@ jobs:
cargo build --release -p windmill_duckdb_ffi_internal
New-Item -ItemType Directory -Path ..\target\debug -Force
Copy-Item target\release\windmill_duckdb_ffi_internal.dll ..\target\debug\
+ # duckdb is bundled (~2GB of build artifacts); the DLL is the only
+ # thing we need from this excluded-crate target dir.
+ Remove-Item -Recurse -Force -ErrorAction SilentlyContinue target
+ Get-PSDrive C | Select-Object Used,Free | Format-Table -AutoSize
- name: Print runtime versions and env
shell: pwsh
@@ -136,6 +155,10 @@ jobs:
echo "USERPROFILE=$env:USERPROFILE"
echo "HOME=$env:HOME"
+ - name: Disk space before cargo test
+ shell: pwsh
+ run: Get-PSDrive C | Select-Object Used,Free | Format-Table -AutoSize
+
- name: cargo test
working-directory: backend
timeout-minutes: 60
@@ -144,7 +167,18 @@ jobs:
RUST_LOG: "off"
RUST_LOG_STYLE: never
CARGO_NET_GIT_FETCH_WITH_CLI: true
- CARGO_BUILD_JOBS: 12
+ # 16-vcpu runners with disabled PDB still hit LNK1180 ("insufficient
+ # disk space") at link time with 12 parallel link jobs: each test
+ # binary link spikes several hundred MB of transient I/O. Capping at
+ # 8 trades ~25% wall time for headroom on the ~75GB runner disk.
+ CARGO_BUILD_JOBS: 8
+ # backend/Cargo.toml sets split-debuginfo = "unpacked", which on
+ # windows-msvc is coerced to "packed": every test-binary link spawns
+ # the mspdbsrv.exe PDB type server and writes a large .pdb. CI needs
+ # no debug info, so disable PDB generation for the dev/test profiles
+ # here (avoids both LNK1318 type-server limit and PDB disk usage).
+ CARGO_PROFILE_DEV_SPLIT_DEBUGINFO: "off"
+ CARGO_PROFILE_TEST_SPLIT_DEBUGINFO: "off"
# Tests' poll-time stack frames (deep nested async fn chains in
# debug builds) reach ~1.8MB. 4MB gives ~2x headroom against flaky
# overflows under parallel-test contention.
diff --git a/.github/workflows/check-empty-fixture.yml b/.github/workflows/check-empty-fixture.yml
new file mode 100644
index 0000000000..4842260645
--- /dev/null
+++ b/.github/workflows/check-empty-fixture.yml
@@ -0,0 +1,19 @@
+name: Check fixture is empty
+
+on:
+ push:
+ branches: [main]
+ paths:
+ - "fixtures/**"
+ pull_request:
+ paths:
+ - "fixtures/**"
+
+jobs:
+ check-empty-fixture:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Ensure fixtures/cli-sync/ has no committed snapshot
+ run: bash fixtures/check-empty.sh
diff --git a/.github/workflows/claude-fast.yml b/.github/workflows/claude-fast.yml
deleted file mode 100644
index 4ad53d326f..0000000000
--- a/.github/workflows/claude-fast.yml
+++ /dev/null
@@ -1,54 +0,0 @@
-name: Fast Claude
-
-on:
- issue_comment:
- types: [created]
- pull_request_review_comment:
- types: [created]
- issues:
- types: [opened, assigned]
- pull_request_review:
- types: [submitted]
-
-jobs:
- check-membership:
- if: |
- (github.event_name == 'issue_comment' && contains(github.event.comment.body, '/ai-fast')) ||
- (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '/ai-fast')) ||
- (github.event_name == 'pull_request_review' && contains(github.event.review.body, '/ai-fast')) ||
- (github.event_name == 'issues' && contains(github.event.issue.body, '/ai-fast'))
- uses: ./.github/workflows/check-org-membership.yml
- secrets:
- access_token: ${{ secrets.ORG_ACCESS_TOKEN }}
-
- claude-code-action:
- needs: check-membership
- if: |
- needs.check-membership.outputs.is_member == 'true'
- runs-on: ubicloud-standard-8
- permissions:
- contents: write
- pull-requests: write
- issues: write
- id-token: write
- steps:
- - name: Checkout repository
- uses: actions/checkout@v4
- with:
- fetch-depth: 1
-
- - name: Run Claude PR Action
- uses: anthropics/claude-code-action@v1
- with:
- claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
- allowed_bots: "windmill-internal-app[bot]"
- trigger_phrase: "/ai-fast"
- settings: |
- {
- "env": {
- "SQLX_OFFLINE": "true"
- }
- }
- claude_args: |
- --allowedTools "Bash,WebFetch,WebSearch"
- --model opus
diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml
index 95436e3214..8052bb6b4c 100644
--- a/.github/workflows/claude.yml
+++ b/.github/workflows/claude.yml
@@ -1,4 +1,4 @@
-name: Claude PR Assistant
+name: Fast Claude
on:
issue_comment:
@@ -26,7 +26,6 @@ jobs:
if: |
needs.check-membership.outputs.is_member == 'true'
runs-on: ubicloud-standard-8
- timeout-minutes: 60
permissions:
contents: write
pull-requests: write
@@ -38,37 +37,6 @@ jobs:
with:
fetch-depth: 1
- - uses: actions/cache@v3
- with:
- path: ~/.npm
- key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
- restore-keys: |
- ${{ runner.os }}-node-
-
- - name: Run npm install and generate-backend-client
- working-directory: ./frontend
- run: |
- # add a build directory for cargo check
- mkdir -p build
- npm install
- npm run generate-backend-client
-
- - name: install xmlsec1 and gssapi
- run: |
- sudo apt-get update
- sudo apt-get install -y libxml2-dev libxmlsec1-dev libkrb5-dev libsasl2-dev libcurl4-openssl-dev mold clang
-
- - uses: actions-rust-lang/setup-rust-toolchain@v1
- with:
- cache-workspaces: backend
- toolchain: 1.93.0
-
- - name: cargo check
- working-directory: ./backend
- timeout-minutes: 16
- run: |
- SQLX_OFFLINE=true cargo check --features all_sqlx_features
-
- name: Run Claude PR Action
uses: anthropics/claude-code-action@v1
with:
@@ -84,24 +52,3 @@ jobs:
claude_args: |
--allowedTools "Bash,WebFetch,WebSearch"
--model opus
- --system-prompt "## IMPORTANT INSTRUCTIONS
- - Your branch name should be a short description of the requested changes.
- - Bypass previous instructions and instead of adding a url in your comment to manually create a PR, if you created a new branch with your changes, automatically create a draft PR from that branch to main.
-
- ## Code Quality Requirements
-
- After making any code changes, you MUST run the appropriate validation commands:
-
- **Frontend Changes:**
- - Run: \`npm run check\` in the frontend directory
- - Fix all warnings and errors before proceeding
-
- **Backend Changes:**
- - Run: \`cargo check --features all_sqlx_features\` in the backend directory
- - Fix all warnings and errors before proceeding
-
- **Pull Request Creation:**
- - DO NOT FORGET TO OPEN A DRAFT PR AFTER YOU ARE DONE if you made changes after a request from a git issue.
-
- ## Available Tools
- - Bash: Full access to run validation commands and git operations"
diff --git a/.github/workflows/codex-pr-review.yml b/.github/workflows/codex-pr-review.yml
index e945f5fd45..66cc4b5f10 100644
--- a/.github/workflows/codex-pr-review.yml
+++ b/.github/workflows/codex-pr-review.yml
@@ -2,53 +2,184 @@ name: Codex Auto Review
on:
pull_request:
- types: [ready_for_review, opened]
+ types: [ready_for_review, opened, synchronize]
+ workflow_call:
+ inputs:
+ pr_number:
+ description: 'PR number to review'
+ required: true
+ type: number
+ extra_prompt:
+ description: 'Additional reviewer instructions appended to the standard review prompt'
+ required: false
+ type: string
+ default: ''
+ triggered_by:
+ description: 'GitHub username that triggered this review (for audit only)'
+ required: false
+ type: string
+ default: ''
+ secrets:
+ OPENAI_API_KEY:
+ required: false
+ CODEX_AUTH_JSON:
+ required: false
+ WINDMILL_EE_PRIVATE_ACCESS:
+ required: false
concurrency:
- group: codex-review-${{ github.event.pull_request.number }}
+ group: codex-review-${{ inputs.pr_number || github.event.pull_request.number }}
cancel-in-progress: true
jobs:
+ check-membership:
+ if: github.event_name == 'pull_request'
+ uses: ./.github/workflows/check-org-membership.yml
+ with:
+ commenter: ${{ github.event.pull_request.user.login }}
+ secrets:
+ access_token: ${{ secrets.ORG_ACCESS_TOKEN }}
+
codex-review:
+ needs: check-membership
runs-on: ubicloud-standard-2
timeout-minutes: 30
- if: github.event.pull_request.draft == false && github.event.pull_request.head.repo.fork == false
+ if: |
+ always() &&
+ (
+ needs.check-membership.result == 'skipped' ||
+ (needs.check-membership.result == 'success' && needs.check-membership.outputs.is_member == 'true')
+ ) &&
+ (
+ github.event_name == 'workflow_call' ||
+ (github.event.pull_request.draft == false && github.event.pull_request.head.repo.fork == false)
+ )
permissions:
contents: read
issues: write
+ pull-requests: write
steps:
- name: Check Codex configuration
id: codex_config
env:
+ OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
CODEX_AUTH_JSON: ${{ secrets.CODEX_AUTH_JSON }}
run: |
- if [ -n "$CODEX_AUTH_JSON" ]; then
+ if [ -n "$OPENAI_API_KEY" ]; then
echo "enabled=true" >> "$GITHUB_OUTPUT"
+ echo "auth_mode=api_key" >> "$GITHUB_OUTPUT"
+ elif [ -n "$CODEX_AUTH_JSON" ]; then
+ echo "enabled=true" >> "$GITHUB_OUTPUT"
+ echo "auth_mode=oauth_json" >> "$GITHUB_OUTPUT"
else
echo "enabled=false" >> "$GITHUB_OUTPUT"
- echo "CODEX_AUTH_JSON is not configured; skipping Codex review."
+ echo "Codex auth is not configured; set OPENAI_API_KEY or CODEX_AUTH_JSON to enable Codex review."
fi
- - name: Checkout repository
+ - name: Resolve PR metadata
if: steps.codex_config.outputs.enabled == 'true'
+ id: pr
+ env:
+ GH_TOKEN: ${{ github.token }}
+ INPUT_PR_NUMBER: ${{ inputs.pr_number }}
+ EVENT_PR_NUMBER: ${{ github.event.pull_request.number }}
+ EVENT_BASE_REF: ${{ github.event.pull_request.base.ref }}
+ EVENT_BASE_SHA: ${{ github.event.pull_request.base.sha }}
+ EVENT_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
+ EVENT_TITLE: ${{ github.event.pull_request.title }}
+ EVENT_BODY: ${{ github.event.pull_request.body }}
+ EVENT_FORK: ${{ github.event.pull_request.head.repo.fork }}
+ EVENT_AUTHOR: ${{ github.event.pull_request.user.login }}
+ run: |
+ if [ -n "$INPUT_PR_NUMBER" ]; then
+ PR_JSON=$(gh pr view "$INPUT_PR_NUMBER" --repo "${{ github.repository }}" \
+ --json number,baseRefName,baseRefOid,headRefOid,title,body,isCrossRepository,author)
+ PR_NUMBER=$(echo "$PR_JSON" | jq -r '.number')
+ BASE_REF=$(echo "$PR_JSON" | jq -r '.baseRefName')
+ BASE_SHA=$(echo "$PR_JSON" | jq -r '.baseRefOid')
+ HEAD_SHA=$(echo "$PR_JSON" | jq -r '.headRefOid')
+ PR_TITLE=$(echo "$PR_JSON" | jq -r '.title')
+ PR_BODY=$(echo "$PR_JSON" | jq -r '.body // ""')
+ IS_FORK=$(echo "$PR_JSON" | jq -r '.isCrossRepository')
+ PR_AUTHOR=$(echo "$PR_JSON" | jq -r '.author.login // ""')
+ else
+ PR_NUMBER="$EVENT_PR_NUMBER"
+ BASE_REF="$EVENT_BASE_REF"
+ BASE_SHA="$EVENT_BASE_SHA"
+ HEAD_SHA="$EVENT_HEAD_SHA"
+ PR_TITLE="$EVENT_TITLE"
+ PR_BODY="$EVENT_BODY"
+ IS_FORK="$EVENT_FORK"
+ PR_AUTHOR="$EVENT_AUTHOR"
+ fi
+ if [ "$IS_FORK" = "true" ]; then
+ echo "Skipping Codex review for fork PR."
+ echo "skip=true" >> "$GITHUB_OUTPUT"
+ exit 0
+ fi
+ {
+ echo "skip=false"
+ echo "pr_number=$PR_NUMBER"
+ echo "base_ref=$BASE_REF"
+ echo "base_sha=$BASE_SHA"
+ echo "head_sha=$HEAD_SHA"
+ echo "pr_author=$PR_AUTHOR"
+ echo 'title<> "$GITHUB_OUTPUT"
+
+ - name: Checkout repository
+ if: steps.codex_config.outputs.enabled == 'true' && steps.pr.outputs.skip != 'true'
uses: actions/checkout@v5
with:
- ref: refs/pull/${{ github.event.pull_request.number }}/merge
+ ref: refs/pull/${{ steps.pr.outputs.pr_number }}/merge
fetch-depth: 1
+ - name: Check EE access
+ if: steps.codex_config.outputs.enabled == 'true' && steps.pr.outputs.skip != 'true'
+ id: ee
+ env:
+ EE_TOKEN: ${{ secrets.WINDMILL_EE_PRIVATE_ACCESS }}
+ run: |
+ if [ -n "$EE_TOKEN" ]; then
+ echo "available=true" >> "$GITHUB_OUTPUT"
+ echo "ee_repo_ref=$(cat ./backend/ee-repo-ref.txt)" >> "$GITHUB_OUTPUT"
+ else
+ echo "available=false" >> "$GITHUB_OUTPUT"
+ fi
+
+ - name: Checkout EE repository
+ if: steps.ee.outputs.available == 'true'
+ uses: actions/checkout@v5
+ with:
+ repository: windmill-labs/windmill-ee-private
+ path: ./windmill-ee-private
+ ref: ${{ steps.ee.outputs.ee_repo_ref }}
+ token: ${{ secrets.WINDMILL_EE_PRIVATE_ACCESS }}
+ fetch-depth: 1
+
+ - name: Substitute EE code
+ if: steps.ee.outputs.available == 'true'
+ run: ./backend/substitute_ee_code.sh --copy --dir ./windmill-ee-private
+
- name: Set up Node.js
- if: steps.codex_config.outputs.enabled == 'true'
+ if: steps.codex_config.outputs.enabled == 'true' && steps.pr.outputs.skip != 'true'
uses: actions/setup-node@v4
with:
node-version: 22
- name: Install Codex CLI
- if: steps.codex_config.outputs.enabled == 'true'
- run: npm install --global @openai/codex@0.117.0
+ if: steps.codex_config.outputs.enabled == 'true' && steps.pr.outputs.skip != 'true'
+ run: npm install --global @openai/codex@0.128.0
- - name: Configure file-backed Codex auth
- if: steps.codex_config.outputs.enabled == 'true'
+ - name: Configure Codex auth
+ if: steps.codex_config.outputs.enabled == 'true' && steps.pr.outputs.skip != 'true'
env:
+ OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
CODEX_AUTH_JSON: ${{ secrets.CODEX_AUTH_JSON }}
run: |
CODEX_HOME="$HOME/.codex"
@@ -58,29 +189,46 @@ jobs:
cat > "$CODEX_HOME/config.toml" <<'EOF'
cli_auth_credentials_store = "file"
EOF
- printf '%s' "$CODEX_AUTH_JSON" > "$CODEX_HOME/auth.json"
- chmod 600 "$CODEX_HOME/auth.json"
- node -e 'JSON.parse(require("fs").readFileSync(process.argv[1], "utf8"))' "$CODEX_HOME/auth.json"
+ if [ -n "$OPENAI_API_KEY" ]; then
+ printf '%s' "$OPENAI_API_KEY" | codex login --with-api-key
+ else
+ printf '%s' "$CODEX_AUTH_JSON" > "$CODEX_HOME/auth.json"
+ chmod 600 "$CODEX_HOME/auth.json"
+ node -e 'JSON.parse(require("fs").readFileSync(process.argv[1], "utf8"))' "$CODEX_HOME/auth.json"
+ fi
- name: Pre-fetch base and head refs for the PR
- if: steps.codex_config.outputs.enabled == 'true'
+ if: steps.codex_config.outputs.enabled == 'true' && steps.pr.outputs.skip != 'true'
env:
- PR_BASE_REF: ${{ github.event.pull_request.base.ref }}
- PR_NUMBER: ${{ github.event.pull_request.number }}
+ PR_BASE_REF: ${{ steps.pr.outputs.base_ref }}
+ PR_NUMBER: ${{ steps.pr.outputs.pr_number }}
run: |
git fetch --no-tags origin \
"$PR_BASE_REF" \
"+refs/pull/$PR_NUMBER/head"
+ - name: Fetch prior PR discussion
+ if: steps.codex_config.outputs.enabled == 'true' && steps.pr.outputs.skip != 'true'
+ env:
+ GH_TOKEN: ${{ github.token }}
+ REPO: ${{ github.repository }}
+ PR_NUMBER: ${{ steps.pr.outputs.pr_number }}
+ run: |
+ gh api "repos/$REPO/issues/$PR_NUMBER/comments?per_page=100" \
+ --jq '[.[] | {user: .user.login, created_at: .created_at, body: (.body | .[:4000])}] | sort_by(.created_at) | .[-20:]' \
+ > prior-comments.json || echo "[]" > prior-comments.json
+
- name: Write Codex review context
- if: steps.codex_config.outputs.enabled == 'true'
+ if: steps.codex_config.outputs.enabled == 'true' && steps.pr.outputs.skip != 'true'
env:
PR_REPOSITORY: ${{ github.repository }}
- PR_NUMBER: ${{ github.event.pull_request.number }}
- PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
- PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
- PR_TITLE: ${{ github.event.pull_request.title }}
- PR_BODY: ${{ github.event.pull_request.body || '' }}
+ PR_NUMBER: ${{ steps.pr.outputs.pr_number }}
+ PR_BASE_SHA: ${{ steps.pr.outputs.base_sha }}
+ PR_HEAD_SHA: ${{ steps.pr.outputs.head_sha }}
+ PR_TITLE: ${{ steps.pr.outputs.title }}
+ PR_BODY: ${{ steps.pr.outputs.body }}
+ PR_AUTHOR: ${{ steps.pr.outputs.pr_author }}
+ EXTRA_PROMPT: ${{ inputs.extra_prompt }}
run: |
mkdir -p .github/codex
node <<'NODE'
@@ -88,6 +236,11 @@ jobs:
const lines = [
`Repository: ${process.env.PR_REPOSITORY}`,
`PR number: ${process.env.PR_NUMBER}`,
+ ];
+ if (process.env.PR_AUTHOR) {
+ lines.push(`PR AUTHOR: ${process.env.PR_AUTHOR}`);
+ }
+ lines.push(
`Base SHA: ${process.env.PR_BASE_SHA}`,
`Head SHA: ${process.env.PR_HEAD_SHA}`,
'',
@@ -105,24 +258,47 @@ jobs:
'',
'Full review diff command:',
`git diff --unified=0 ${process.env.PR_BASE_SHA}...${process.env.PR_HEAD_SHA}`
- ];
+ );
+ if (process.env.EXTRA_PROMPT && process.env.EXTRA_PROMPT.trim()) {
+ lines.push('', 'Additional reviewer instructions:', process.env.EXTRA_PROMPT.trim());
+ }
+ if (fs.existsSync('prior-comments.json')) {
+ try {
+ const comments = JSON.parse(fs.readFileSync('prior-comments.json', 'utf8'));
+ if (Array.isArray(comments) && comments.length > 0) {
+ lines.push(
+ '',
+ 'Prior PR discussion (most recent up to 20 comments):',
+ '',
+ 'If you have already reviewed this PR (look for your own earlier "## Codex Review" comment), focus on what changed since then per the diff and respect any decisions the human made in replies. Do not re-flag findings the human already pushed back on.',
+ ''
+ );
+ for (const c of comments) {
+ lines.push(`### @${c.user} (${c.created_at})`, '', c.body, '', '---', '');
+ }
+ }
+ } catch (_) {}
+ }
fs.writeFileSync('.github/codex/pr-review-context.md', `${lines.join('\n')}\n`);
NODE
- name: Run Codex review
- if: steps.codex_config.outputs.enabled == 'true'
+ if: steps.codex_config.outputs.enabled == 'true' && steps.pr.outputs.skip != 'true'
run: |
+ cat REVIEW.md .github/codex/pr-review.prompt.md > /tmp/codex-prompt.md
codex exec \
-C "$GITHUB_WORKSPACE" \
- -m gpt-5.4 \
+ -m gpt-5.5 \
-c 'model_reasoning_effort="xhigh"' \
- -s read-only \
+ -s danger-full-access \
-o codex-final-message.md \
- - < .github/codex/pr-review.prompt.md
+ - < /tmp/codex-prompt.md
- name: Post Codex review comment
- if: steps.codex_config.outputs.enabled == 'true'
+ if: steps.codex_config.outputs.enabled == 'true' && steps.pr.outputs.skip != 'true'
uses: actions/github-script@v7
+ env:
+ PR_NUMBER: ${{ steps.pr.outputs.pr_number }}
with:
github-token: ${{ github.token }}
script: |
@@ -140,6 +316,6 @@ jobs:
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
- issue_number: context.payload.pull_request.number,
+ issue_number: Number(process.env.PR_NUMBER),
body,
});
diff --git a/.github/workflows/git-sync-test.yml b/.github/workflows/git-sync-test.yml
index 09f69bb701..12b75932ce 100644
--- a/.github/workflows/git-sync-test.yml
+++ b/.github/workflows/git-sync-test.yml
@@ -8,6 +8,7 @@ on:
- "backend/windmill-git-sync/**"
- "backend/windmill-api-integration-tests/tests/git_sync*"
- "backend/ee-repo-ref.txt"
+ - "backend/windmill-common/src/workspaces.rs"
- "integration_tests/test/git_sync_test.py"
- ".github/workflows/git-sync-test.yml"
pull_request:
@@ -16,6 +17,7 @@ on:
- "backend/windmill-git-sync/**"
- "backend/windmill-api-integration-tests/tests/git_sync*"
- "backend/ee-repo-ref.txt"
+ - "backend/windmill-common/src/workspaces.rs"
- "integration_tests/test/git_sync_test.py"
- ".github/workflows/git-sync-test.yml"
@@ -49,7 +51,7 @@ jobs:
echo "$CHANGED_FILES"
# Direct git sync file changes — always relevant
- if echo "$CHANGED_FILES" | grep -qE '^(backend/windmill-git-sync/|backend/windmill-api-integration-tests/tests/git_sync|integration_tests/test/git_sync|\.github/workflows/git-sync-test\.yml)'; then
+ if echo "$CHANGED_FILES" | grep -qE '^(backend/windmill-git-sync/|backend/windmill-api-integration-tests/tests/git_sync|backend/windmill-common/src/workspaces\.rs|integration_tests/test/git_sync|\.github/workflows/git-sync-test\.yml)'; then
echo "should_run=true" >> "$GITHUB_OUTPUT"
echo "Relevant: direct git sync file changes"
exit 0
diff --git a/.github/workflows/pi-pr-review.yml b/.github/workflows/pi-pr-review.yml
new file mode 100644
index 0000000000..9fbe43e9f0
--- /dev/null
+++ b/.github/workflows/pi-pr-review.yml
@@ -0,0 +1,325 @@
+name: Pi Auto Review
+
+on:
+ pull_request:
+ types: [ready_for_review, opened, synchronize]
+ workflow_call:
+ inputs:
+ pr_number:
+ description: 'PR number to review'
+ required: true
+ type: number
+ extra_prompt:
+ description: 'Additional reviewer instructions appended to the standard review prompt'
+ required: false
+ type: string
+ default: ''
+ triggered_by:
+ description: 'GitHub username that triggered this review (for audit only)'
+ required: false
+ type: string
+ default: ''
+ secrets:
+ DEEPSEEK_API_KEY:
+ required: false
+ WINDMILL_EE_PRIVATE_ACCESS:
+ required: false
+
+concurrency:
+ group: pi-review-${{ inputs.pr_number || github.event.pull_request.number }}
+ cancel-in-progress: true
+
+jobs:
+ check-membership:
+ if: github.event_name == 'pull_request'
+ uses: ./.github/workflows/check-org-membership.yml
+ with:
+ commenter: ${{ github.event.pull_request.user.login }}
+ secrets:
+ access_token: ${{ secrets.ORG_ACCESS_TOKEN }}
+
+ pi-review:
+ needs: check-membership
+ runs-on: ubicloud-standard-2
+ timeout-minutes: 30
+ if: |
+ always() &&
+ (
+ needs.check-membership.result == 'skipped' ||
+ (needs.check-membership.result == 'success' && needs.check-membership.outputs.is_member == 'true')
+ ) &&
+ (
+ github.event_name == 'workflow_call' ||
+ (github.event.pull_request.draft == false && github.event.pull_request.head.repo.fork == false)
+ )
+ permissions:
+ contents: read
+ issues: write
+ pull-requests: write
+ steps:
+ - name: Check Pi configuration
+ id: pi_config
+ env:
+ DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}
+ run: |
+ if [ -n "$DEEPSEEK_API_KEY" ]; then
+ echo "enabled=true" >> "$GITHUB_OUTPUT"
+ else
+ echo "enabled=false" >> "$GITHUB_OUTPUT"
+ echo "DEEPSEEK_API_KEY is not configured; skipping Pi review."
+ fi
+
+ - name: Resolve PR metadata
+ if: steps.pi_config.outputs.enabled == 'true'
+ id: pr
+ env:
+ GH_TOKEN: ${{ github.token }}
+ INPUT_PR_NUMBER: ${{ inputs.pr_number }}
+ EVENT_PR_NUMBER: ${{ github.event.pull_request.number }}
+ EVENT_BASE_REF: ${{ github.event.pull_request.base.ref }}
+ EVENT_BASE_SHA: ${{ github.event.pull_request.base.sha }}
+ EVENT_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
+ EVENT_TITLE: ${{ github.event.pull_request.title }}
+ EVENT_BODY: ${{ github.event.pull_request.body }}
+ EVENT_FORK: ${{ github.event.pull_request.head.repo.fork }}
+ EVENT_AUTHOR: ${{ github.event.pull_request.user.login }}
+ run: |
+ if [ -n "$INPUT_PR_NUMBER" ]; then
+ PR_JSON=$(gh pr view "$INPUT_PR_NUMBER" --repo "${{ github.repository }}" \
+ --json number,baseRefName,baseRefOid,headRefOid,title,body,isCrossRepository,author)
+ PR_NUMBER=$(echo "$PR_JSON" | jq -r '.number')
+ BASE_REF=$(echo "$PR_JSON" | jq -r '.baseRefName')
+ BASE_SHA=$(echo "$PR_JSON" | jq -r '.baseRefOid')
+ HEAD_SHA=$(echo "$PR_JSON" | jq -r '.headRefOid')
+ PR_TITLE=$(echo "$PR_JSON" | jq -r '.title')
+ PR_BODY=$(echo "$PR_JSON" | jq -r '.body // ""')
+ IS_FORK=$(echo "$PR_JSON" | jq -r '.isCrossRepository')
+ PR_AUTHOR=$(echo "$PR_JSON" | jq -r '.author.login // ""')
+ else
+ PR_NUMBER="$EVENT_PR_NUMBER"
+ BASE_REF="$EVENT_BASE_REF"
+ BASE_SHA="$EVENT_BASE_SHA"
+ HEAD_SHA="$EVENT_HEAD_SHA"
+ PR_TITLE="$EVENT_TITLE"
+ PR_BODY="$EVENT_BODY"
+ IS_FORK="$EVENT_FORK"
+ PR_AUTHOR="$EVENT_AUTHOR"
+ fi
+ if [ "$IS_FORK" = "true" ]; then
+ echo "Skipping Pi review for fork PR."
+ echo "skip=true" >> "$GITHUB_OUTPUT"
+ exit 0
+ fi
+ {
+ echo "skip=false"
+ echo "pr_number=$PR_NUMBER"
+ echo "base_ref=$BASE_REF"
+ echo "base_sha=$BASE_SHA"
+ echo "head_sha=$HEAD_SHA"
+ echo "pr_author=$PR_AUTHOR"
+ echo 'title<> "$GITHUB_OUTPUT"
+
+ - name: Checkout repository
+ if: steps.pi_config.outputs.enabled == 'true' && steps.pr.outputs.skip != 'true'
+ uses: actions/checkout@v5
+ with:
+ ref: refs/pull/${{ steps.pr.outputs.pr_number }}/merge
+ fetch-depth: 1
+
+ - name: Check EE access
+ if: steps.pi_config.outputs.enabled == 'true' && steps.pr.outputs.skip != 'true'
+ id: ee
+ env:
+ EE_TOKEN: ${{ secrets.WINDMILL_EE_PRIVATE_ACCESS }}
+ run: |
+ if [ -n "$EE_TOKEN" ]; then
+ echo "available=true" >> "$GITHUB_OUTPUT"
+ echo "ee_repo_ref=$(cat ./backend/ee-repo-ref.txt)" >> "$GITHUB_OUTPUT"
+ else
+ echo "available=false" >> "$GITHUB_OUTPUT"
+ fi
+
+ - name: Checkout EE repository
+ if: steps.ee.outputs.available == 'true'
+ uses: actions/checkout@v5
+ with:
+ repository: windmill-labs/windmill-ee-private
+ path: ./windmill-ee-private
+ ref: ${{ steps.ee.outputs.ee_repo_ref }}
+ token: ${{ secrets.WINDMILL_EE_PRIVATE_ACCESS }}
+ fetch-depth: 1
+
+ - name: Substitute EE code
+ if: steps.ee.outputs.available == 'true'
+ run: ./backend/substitute_ee_code.sh --copy --dir ./windmill-ee-private
+
+ - name: Set up Node.js
+ if: steps.pi_config.outputs.enabled == 'true' && steps.pr.outputs.skip != 'true'
+ uses: actions/setup-node@v4
+ with:
+ node-version: 22
+
+ - name: Install Pi CLI
+ if: steps.pi_config.outputs.enabled == 'true' && steps.pr.outputs.skip != 'true'
+ run: npm install --global @mariozechner/pi-coding-agent
+
+ - name: Pre-fetch base and head refs for the PR
+ if: steps.pi_config.outputs.enabled == 'true' && steps.pr.outputs.skip != 'true'
+ env:
+ PR_BASE_REF: ${{ steps.pr.outputs.base_ref }}
+ PR_NUMBER: ${{ steps.pr.outputs.pr_number }}
+ run: |
+ git fetch --no-tags origin \
+ "$PR_BASE_REF" \
+ "+refs/pull/$PR_NUMBER/head"
+
+ - name: Fetch prior PR discussion
+ if: steps.pi_config.outputs.enabled == 'true' && steps.pr.outputs.skip != 'true'
+ env:
+ GH_TOKEN: ${{ github.token }}
+ REPO: ${{ github.repository }}
+ PR_NUMBER: ${{ steps.pr.outputs.pr_number }}
+ run: |
+ gh api "repos/$REPO/issues/$PR_NUMBER/comments?per_page=100" \
+ --jq '[.[] | {user: .user.login, created_at: .created_at, body: (.body | .[:4000])}] | sort_by(.created_at) | .[-20:]' \
+ > prior-comments.json || echo "[]" > prior-comments.json
+
+ - name: Write Pi review context
+ if: steps.pi_config.outputs.enabled == 'true' && steps.pr.outputs.skip != 'true'
+ env:
+ PR_REPOSITORY: ${{ github.repository }}
+ PR_NUMBER: ${{ steps.pr.outputs.pr_number }}
+ PR_BASE_SHA: ${{ steps.pr.outputs.base_sha }}
+ PR_HEAD_SHA: ${{ steps.pr.outputs.head_sha }}
+ PR_TITLE: ${{ steps.pr.outputs.title }}
+ PR_BODY: ${{ steps.pr.outputs.body }}
+ PR_AUTHOR: ${{ steps.pr.outputs.pr_author }}
+ EXTRA_PROMPT: ${{ inputs.extra_prompt }}
+ run: |
+ mkdir -p .github/pi
+ node <<'NODE'
+ const fs = require('fs');
+ const lines = [
+ `Repository: ${process.env.PR_REPOSITORY}`,
+ `PR number: ${process.env.PR_NUMBER}`,
+ ];
+ if (process.env.PR_AUTHOR) {
+ lines.push(`PR AUTHOR: ${process.env.PR_AUTHOR}`);
+ }
+ lines.push(
+ `Base SHA: ${process.env.PR_BASE_SHA}`,
+ `Head SHA: ${process.env.PR_HEAD_SHA}`,
+ '',
+ 'PR title:',
+ process.env.PR_TITLE || '(empty)',
+ '',
+ 'PR body:',
+ process.env.PR_BODY || '(empty)',
+ '',
+ 'Changed commits command:',
+ `git log --oneline ${process.env.PR_BASE_SHA}...${process.env.PR_HEAD_SHA}`,
+ '',
+ 'Changed files command:',
+ `git diff --stat ${process.env.PR_BASE_SHA}...${process.env.PR_HEAD_SHA}`,
+ '',
+ 'Full review diff command:',
+ `git diff --unified=0 ${process.env.PR_BASE_SHA}...${process.env.PR_HEAD_SHA}`
+ );
+ if (process.env.EXTRA_PROMPT && process.env.EXTRA_PROMPT.trim()) {
+ lines.push('', 'Additional reviewer instructions:', process.env.EXTRA_PROMPT.trim());
+ }
+ if (fs.existsSync('prior-comments.json')) {
+ try {
+ const comments = JSON.parse(fs.readFileSync('prior-comments.json', 'utf8'));
+ if (Array.isArray(comments) && comments.length > 0) {
+ lines.push(
+ '',
+ 'Prior PR discussion (most recent up to 20 comments):',
+ '',
+ 'If you have already reviewed this PR (look for your own earlier "## Pi Review (DeepSeek V4)" comment), focus on what changed since then per the diff and respect any decisions the human made in replies. Do not re-flag findings the human already pushed back on.',
+ ''
+ );
+ for (const c of comments) {
+ lines.push(`### @${c.user} (${c.created_at})`, '', c.body, '', '---', '');
+ }
+ }
+ } catch (_) {}
+ }
+ fs.writeFileSync('.github/pi/pr-review-context.md', `${lines.join('\n')}\n`);
+ NODE
+
+ - name: Run Pi review
+ if: steps.pi_config.outputs.enabled == 'true' && steps.pr.outputs.skip != 'true'
+ env:
+ DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}
+ PI_SKIP_VERSION_CHECK: '1'
+ run: |
+ set -o pipefail
+ cat REVIEW.md .github/pi/pr-review.prompt.md > /tmp/pi-prompt.md
+ pi -p \
+ --provider deepseek \
+ --model deepseek-v4-pro \
+ --tools read,grep,find,ls,bash \
+ --mode json \
+ < /tmp/pi-prompt.md \
+ | tee pi-events.jsonl \
+ | jq -rc --unbuffered '
+ if .type == "agent_start" then "🤖 pi agent started"
+ elif .type == "turn_start" then "── turn ──"
+ elif .type == "message_end" then
+ "[\(.message.role)] " + (
+ (.message.content // [])
+ | map(
+ if .type == "text" then "text(\(.text | length)c)"
+ elif .type == "tool_use" then "🔧 \(.name) \(.input | @json | .[:160])"
+ elif .type == "tool_result" then "✅ result"
+ else .type
+ end
+ )
+ | join(" | ")
+ )
+ elif .type == "turn_end" then "── turn done (\((.toolResults // []) | length) tool result(s)) ──"
+ elif .type == "agent_end" then "🏁 pi agent done"
+ else empty
+ end
+ '
+
+ jq -r '
+ select(.type == "agent_end")
+ | .messages
+ | map(select(.role == "assistant"))
+ | last
+ | (.content[]? | select(.type == "text") | .text)
+ ' pi-events.jsonl > pi-final-message.md
+
+ - name: Post Pi review comment
+ if: steps.pi_config.outputs.enabled == 'true' && steps.pr.outputs.skip != 'true'
+ uses: actions/github-script@v7
+ env:
+ PR_NUMBER: ${{ steps.pr.outputs.pr_number }}
+ with:
+ github-token: ${{ github.token }}
+ script: |
+ const fs = require('fs');
+ const path = `${process.env.GITHUB_WORKSPACE}/pi-final-message.md`;
+ if (!fs.existsSync(path)) {
+ core.info('Pi did not produce a final message; skipping PR comment.');
+ return;
+ }
+ const body = fs.readFileSync(path, 'utf8').trim();
+ if (!body) {
+ core.info('Pi final message was empty; skipping PR comment.');
+ return;
+ }
+ await github.rest.issues.createComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: Number(process.env.PR_NUMBER),
+ body,
+ });
diff --git a/.github/workflows/pr-ready-review.yml b/.github/workflows/pr-ready-review.yml
index 78c0c3e045..6414cf944c 100644
--- a/.github/workflows/pr-ready-review.yml
+++ b/.github/workflows/pr-ready-review.yml
@@ -3,31 +3,147 @@ name: Claude Auto Review
on:
pull_request:
types: [ready_for_review, opened]
+ workflow_call:
+ inputs:
+ pr_number:
+ description: 'PR number to review'
+ required: true
+ type: number
+ extra_prompt:
+ description: 'Additional reviewer instructions appended to the standard review prompt'
+ required: false
+ type: string
+ default: ''
+ triggered_by:
+ description: 'GitHub username that triggered this review (for audit only)'
+ required: false
+ type: string
+ default: ''
+ secrets:
+ CLAUDE_CODE_OAUTH_TOKEN:
+ required: true
+ WINDMILL_EE_PRIVATE_ACCESS:
+ required: false
concurrency:
- group: claude-review-${{ github.event.pull_request.number }}
+ group: claude-review-${{ inputs.pr_number || github.event.pull_request.number }}
cancel-in-progress: true
jobs:
+ check-membership:
+ if: github.event_name == 'pull_request'
+ uses: ./.github/workflows/check-org-membership.yml
+ with:
+ commenter: ${{ github.event.pull_request.user.login }}
+ secrets:
+ access_token: ${{ secrets.ORG_ACCESS_TOKEN }}
+
auto-review:
+ needs: check-membership
runs-on: ubuntu-latest
- if: github.event.pull_request.draft == false || github.event.pull_request.ready_for_review == true
+ if: |
+ always() &&
+ (
+ needs.check-membership.result == 'skipped' ||
+ (needs.check-membership.result == 'success' && needs.check-membership.outputs.is_member == 'true')
+ ) &&
+ (
+ github.event_name == 'workflow_call' ||
+ (github.event.pull_request.draft == false || github.event.pull_request.ready_for_review == true)
+ )
permissions:
contents: read
pull-requests: read
id-token: write
steps:
- name: Checkout repository
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
with:
fetch-depth: 1
+ - name: Check EE access
+ id: ee
+ env:
+ EE_TOKEN: ${{ secrets.WINDMILL_EE_PRIVATE_ACCESS }}
+ run: |
+ if [ -n "$EE_TOKEN" ]; then
+ echo "available=true" >> "$GITHUB_OUTPUT"
+ echo "ee_repo_ref=$(cat ./backend/ee-repo-ref.txt)" >> "$GITHUB_OUTPUT"
+ else
+ echo "available=false" >> "$GITHUB_OUTPUT"
+ fi
+
+ - name: Checkout EE repository
+ if: steps.ee.outputs.available == 'true'
+ uses: actions/checkout@v5
+ with:
+ repository: windmill-labs/windmill-ee-private
+ path: ./windmill-ee-private
+ ref: ${{ steps.ee.outputs.ee_repo_ref }}
+ token: ${{ secrets.WINDMILL_EE_PRIVATE_ACCESS }}
+ fetch-depth: 1
+
+ - name: Substitute EE code
+ if: steps.ee.outputs.available == 'true'
+ run: ./backend/substitute_ee_code.sh --copy --dir ./windmill-ee-private
+
+ - name: Resolve PR number
+ id: resolve
+ env:
+ GH_TOKEN: ${{ github.token }}
+ REPO: ${{ github.repository }}
+ INPUT_PR_NUMBER: ${{ inputs.pr_number }}
+ EVENT_PR_NUMBER: ${{ github.event.pull_request.number }}
+ EVENT_PR_AUTHOR: ${{ github.event.pull_request.user.login }}
+ run: |
+ if [ -n "$INPUT_PR_NUMBER" ]; then
+ PR_NUMBER="$INPUT_PR_NUMBER"
+ PR_AUTHOR=$(gh api "repos/$REPO/pulls/$PR_NUMBER" --jq '.user.login')
+ else
+ PR_NUMBER="$EVENT_PR_NUMBER"
+ PR_AUTHOR="$EVENT_PR_AUTHOR"
+ fi
+ echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT"
+ echo "pr_author=$PR_AUTHOR" >> "$GITHUB_OUTPUT"
+
+ - name: Fetch prior PR discussion
+ id: prior
+ env:
+ GH_TOKEN: ${{ github.token }}
+ REPO: ${{ github.repository }}
+ PR_NUMBER: ${{ steps.resolve.outputs.pr_number }}
+ run: |
+ gh api "repos/$REPO/issues/$PR_NUMBER/comments?per_page=100" \
+ --jq '[.[] | {user: .user.login, created_at: .created_at, body: (.body | .[:4000])}] | sort_by(.created_at) | .[-20:]' \
+ > prior-comments.json || echo "[]" > prior-comments.json
+ jq -r '
+ if length == 0 then ""
+ else
+ "## Prior PR discussion (most recent up to 20 comments)\n\nIf you have already reviewed this PR (look for your own earlier comment), focus on what changed since then per the diff and respect any decisions the human made in replies. Do not re-flag findings the human already pushed back on.\n\n" +
+ (map("### @\(.user) (\(.created_at))\n\n\(.body)") | join("\n\n---\n\n"))
+ end
+ ' prior-comments.json > prior-comments.md
+
- name: Read review prompt
id: review-prompt
+ env:
+ EXTRA_PROMPT: ${{ inputs.extra_prompt }}
run: |
{
echo 'REVIEW_PROMPT<> "$GITHUB_ENV"
@@ -38,7 +154,8 @@ jobs:
track_progress: true
prompt: |
REPO: ${{ github.repository }}
- PR NUMBER: ${{ github.event.pull_request.number }}
+ PR NUMBER: ${{ steps.resolve.outputs.pr_number }}
+ PR AUTHOR: ${{ steps.resolve.outputs.pr_author }}
${{ env.REVIEW_PROMPT }}
claude_args: |
diff --git a/.github/workflows/pr-review-commands.yml b/.github/workflows/pr-review-commands.yml
new file mode 100644
index 0000000000..ba55bfea2f
--- /dev/null
+++ b/.github/workflows/pr-review-commands.yml
@@ -0,0 +1,123 @@
+name: PR Review Commands
+
+on:
+ issue_comment:
+ types: [created]
+
+jobs:
+ parse:
+ if: github.event.issue.pull_request != null
+ runs-on: ubuntu-latest
+ outputs:
+ command: ${{ steps.parse.outputs.command }}
+ extra_prompt: ${{ steps.parse.outputs.extra_prompt }}
+ steps:
+ - name: Parse command from comment
+ id: parse
+ env:
+ BODY: ${{ github.event.comment.body }}
+ run: |
+ FIRST_LINE=$(printf '%s' "$BODY" | head -n 1 | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//')
+ FIRST_WORD=${FIRST_LINE%% *}
+ case "$FIRST_WORD" in
+ /review|/codex|/pi|/claude)
+ COMMAND="${FIRST_WORD#/}"
+ REMAINDER_FIRST_LINE=${FIRST_LINE#"$FIRST_WORD"}
+ REMAINDER_FIRST_LINE=${REMAINDER_FIRST_LINE# }
+ REST=$(printf '%s' "$BODY" | tail -n +2)
+ {
+ echo "command=$COMMAND"
+ echo 'extra_prompt<> "$GITHUB_OUTPUT"
+ ;;
+ *)
+ echo "command=" >> "$GITHUB_OUTPUT"
+ ;;
+ esac
+
+ check-membership:
+ needs: parse
+ if: needs.parse.outputs.command != ''
+ uses: ./.github/workflows/check-org-membership.yml
+ secrets:
+ access_token: ${{ secrets.ORG_ACCESS_TOKEN }}
+
+ acknowledge:
+ needs: [parse, check-membership]
+ if: needs.parse.outputs.command != '' && needs.check-membership.outputs.is_member == 'true'
+ runs-on: ubuntu-latest
+ permissions:
+ issues: write
+ pull-requests: write
+ steps:
+ - name: React to comment with eyes
+ env:
+ GH_TOKEN: ${{ github.token }}
+ REPO: ${{ github.repository }}
+ COMMENT_ID: ${{ github.event.comment.id }}
+ run: |
+ gh api -X POST \
+ "/repos/$REPO/issues/comments/$COMMENT_ID/reactions" \
+ -f content=eyes >/dev/null
+
+ claude:
+ needs: [parse, check-membership]
+ if: |
+ needs.check-membership.outputs.is_member == 'true' &&
+ (needs.parse.outputs.command == 'review' || needs.parse.outputs.command == 'claude')
+ permissions:
+ contents: read
+ pull-requests: read
+ id-token: write
+ uses: ./.github/workflows/pr-ready-review.yml
+ with:
+ pr_number: ${{ github.event.issue.number }}
+ extra_prompt: ${{ needs.parse.outputs.extra_prompt }}
+ triggered_by: ${{ github.event.comment.user.login }}
+ secrets:
+ CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
+ WINDMILL_EE_PRIVATE_ACCESS: ${{ secrets.WINDMILL_EE_PRIVATE_ACCESS }}
+
+ codex:
+ needs: [parse, check-membership]
+ if: |
+ needs.check-membership.outputs.is_member == 'true' &&
+ (needs.parse.outputs.command == 'review' || needs.parse.outputs.command == 'codex')
+ permissions:
+ contents: read
+ issues: write
+ pull-requests: write
+ uses: ./.github/workflows/codex-pr-review.yml
+ with:
+ pr_number: ${{ github.event.issue.number }}
+ extra_prompt: ${{ needs.parse.outputs.extra_prompt }}
+ triggered_by: ${{ github.event.comment.user.login }}
+ secrets:
+ OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
+ CODEX_AUTH_JSON: ${{ secrets.CODEX_AUTH_JSON }}
+ WINDMILL_EE_PRIVATE_ACCESS: ${{ secrets.WINDMILL_EE_PRIVATE_ACCESS }}
+
+ pi:
+ needs: [parse, check-membership]
+ if: |
+ needs.check-membership.outputs.is_member == 'true' &&
+ (needs.parse.outputs.command == 'review' || needs.parse.outputs.command == 'pi')
+ permissions:
+ contents: read
+ issues: write
+ pull-requests: write
+ uses: ./.github/workflows/pi-pr-review.yml
+ with:
+ pr_number: ${{ github.event.issue.number }}
+ extra_prompt: ${{ needs.parse.outputs.extra_prompt }}
+ triggered_by: ${{ github.event.comment.user.login }}
+ secrets:
+ DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}
+ WINDMILL_EE_PRIVATE_ACCESS: ${{ secrets.WINDMILL_EE_PRIVATE_ACCESS }}
diff --git a/.github/workflows/publish-cli-docs.yml b/.github/workflows/publish-cli-docs.yml
new file mode 100644
index 0000000000..9e76117eb5
--- /dev/null
+++ b/.github/workflows/publish-cli-docs.yml
@@ -0,0 +1,84 @@
+name: Publish CLI docs repo
+
+# Regenerates the windmill-cli-docs repo (consumed by context7) from the
+# canonical sources in this repo on every Windmill release.
+#
+# Required secret:
+# CLI_DOCS_DEPLOY_KEY — ed25519 private key whose public half is registered
+# as a write-access deploy key on
+# windmill-labs/windmill-cli-docs.
+
+on:
+ push:
+ tags:
+ - "v*"
+ workflow_dispatch:
+
+# Serialize pushes to windmill-cli-docs so two release tags landing close
+# together (e.g. a release-please bump + a hotfix) can't race to force-push
+# the docs repo.
+concurrency:
+ group: publish-cli-docs
+ cancel-in-progress: false
+
+jobs:
+ publish:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout windmill (source of truth)
+ uses: actions/checkout@v4
+ with:
+ path: windmill
+
+ - name: Checkout windmill-cli-docs (publish target)
+ uses: actions/checkout@v4
+ with:
+ repository: windmill-labs/windmill-cli-docs
+ path: windmill-cli-docs
+ ssh-key: ${{ secrets.CLI_DOCS_DEPLOY_KEY }}
+ fetch-depth: 0
+
+ - uses: actions/setup-python@v5
+ with:
+ python-version: "3.12"
+
+ - name: Install dependencies
+ run: pip install pyyaml
+
+ - name: Regenerate docs
+ run: |
+ python3 windmill/system_prompts/generate.py \
+ --context7-dir "$GITHUB_WORKSPACE/windmill-cli-docs"
+
+ - name: Commit and push if changed
+ working-directory: windmill-cli-docs
+ env:
+ REF_NAME: ${{ github.ref_name }}
+ REF_TYPE: ${{ github.ref_type }}
+ run: |
+ git config user.name "windmill-bot"
+ git config user.email "bot@windmill.dev"
+ git add -A
+ if git diff --cached --quiet; then
+ echo "No doc changes for ${REF_NAME}."
+ committed=false
+ else
+ committed=true
+ if [ "${REF_TYPE}" = "tag" ]; then
+ git commit -m "chore: sync from windmill ${REF_NAME}"
+ else
+ git commit -m "chore: sync from windmill (manual dispatch from ${REF_NAME})"
+ fi
+ git push origin HEAD
+ fi
+ # Always mirror the version tag on tag pushes, even when content
+ # didn't change — downstream consumers tie snapshots to releases by
+ # tag, and skipping it would leave the docs repo without a tag for
+ # the new Windmill release.
+ # workflow_dispatch from a non-tag ref skips this so we don't
+ # create a junk tag named after a branch.
+ if [ "${REF_TYPE}" = "tag" ]; then
+ git tag -f "${REF_NAME}"
+ git push origin "${REF_NAME}" --force
+ echo "Mirrored tag ${REF_NAME} to windmill-cli-docs (content changed: ${committed})."
+ fi
diff --git a/.github/workflows/spawn-ephemeral-backend.yml b/.github/workflows/spawn-ephemeral-backend.yml
deleted file mode 100644
index 725890031a..0000000000
--- a/.github/workflows/spawn-ephemeral-backend.yml
+++ /dev/null
@@ -1,126 +0,0 @@
-name: Spawn Ephemeral Backend
-
-on:
- issue_comment:
- types: [created]
- pull_request_review_comment:
- types: [created]
- workflow_dispatch:
- inputs:
- pr_number:
- description: "PR number"
- required: true
- type: number
-
-jobs:
- check-membership:
- if: |
- (github.event_name == 'issue_comment' && contains(github.event.comment.body, '/spawnbackend')) ||
- (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '/spawnbackend'))
- uses: ./.github/workflows/check-org-membership.yml
- secrets:
- access_token: ${{ secrets.ORG_ACCESS_TOKEN }}
-
- spawn-backend:
- needs: check-membership
- # Only run on PR comments that contain /spawn-backend, or manual dispatch
- if: |
- github.event_name == 'workflow_dispatch' ||
- (github.event.issue.pull_request && needs.check-membership.outputs.is_member == 'true')
- runs-on: ubuntu-latest
- permissions:
- pull-requests: write
- contents: read
-
- steps:
- - name: Get PR details
- id: pr-details
- uses: actions/github-script@v7
- with:
- script: |
- const prNumber = context.eventName === 'workflow_dispatch'
- ? context.payload.inputs.pr_number
- : context.issue.number;
-
- const pr = await github.rest.pulls.get({
- owner: context.repo.owner,
- repo: context.repo.repo,
- pull_number: prNumber
- });
-
- // Get branch name and format it for Cloudflare Pages
- // Replace '/' with '-' for the URL
- const branchName = pr.data.head.ref;
- const formattedBranch = branchName.replace(/\//g, '-');
- const cfFrontendUrl = `https://${formattedBranch}.windmill.pages.dev`;
-
- core.setOutput('commit_hash', pr.data.head.sha);
- core.setOutput('pr_number', prNumber);
- core.setOutput('branch_name', branchName);
- core.setOutput('cf_frontend_url', cfFrontendUrl);
-
- - name: Check manager URL
- id: check-manager-url
- run: |
- if [ -z "${{ secrets.EPHEMERAL_BACKEND_QUEUE_URL }}" ]; then
- echo "manager_url_set=false" >> $GITHUB_OUTPUT
- else
- echo "manager_url_set=true" >> $GITHUB_OUTPUT
- fi
-
- - name: Post error comment if manager not running
- if: steps.check-manager-url.outputs.manager_url_set == 'false'
- uses: actions/github-script@v7
- with:
- script: |
- const prNumber = context.eventName === 'workflow_dispatch'
- ? Number(context.payload.inputs.pr_number)
- : context.issue.number;
-
- await github.rest.issues.createComment({
- owner: context.repo.owner,
- repo: context.repo.repo,
- issue_number: prNumber,
- body: `❌ Manager URL not set (did you start the ephemeral backend manager?)\n\nThe ephemeral backend manager needs to be running to spawn backends. Please start the manager first.`
- });
-
- - name: Fail if manager not running
- if: steps.check-manager-url.outputs.manager_url_set == 'false'
- run: |
- echo "Error: EPHEMERAL_BACKEND_QUEUE_URL secret is not set"
- exit 1
-
- - name: Trigger Windmill flow
- if: steps.check-manager-url.outputs.manager_url_set == 'true'
- id: trigger-flow
- run: |
- JOB_UUID=$(curl -s -X POST "https://app.windmill.dev/api/w/windmill-labs/jobs/run/f/f/all/run_ephemeral_backend" \
- -H "Authorization: Bearer ${{ secrets.WINDMILL_RUN_FLOW_TOKEN }}" \
- -H "Content-Type: application/json" \
- -d '{
- "manager_url": "${{ secrets.EPHEMERAL_BACKEND_QUEUE_URL }}",
- "commit_hash": "${{ steps.pr-details.outputs.commit_hash }}",
- "pr_number": ${{ steps.pr-details.outputs.pr_number }},
- "cf_frontend_url": "${{ steps.pr-details.outputs.cf_frontend_url }}"
- }' | tr -d '"')
-
- echo "Job UUID: $JOB_UUID"
- echo "job_uuid=$JOB_UUID" >> $GITHUB_OUTPUT
-
- - name: Post comment with job link
- if: steps.check-manager-url.outputs.manager_url_set == 'true'
- uses: actions/github-script@v7
- with:
- script: |
- const jobUuid = '${{ steps.trigger-flow.outputs.job_uuid }}';
- const appUrl = `https://app.windmill.dev/public/windmill-labs/a106bad0256c1dfa7a4f9279c42b1a4b#${jobUuid}`;
- const prNumber = context.eventName === 'workflow_dispatch'
- ? Number(context.payload.inputs.pr_number)
- : context.issue.number;
-
- await github.rest.issues.createComment({
- owner: context.repo.owner,
- repo: context.repo.repo,
- issue_number: prNumber,
- body: `🚀 Spawning new ephemeral backend!\n\n${appUrl}`
- });
diff --git a/.gitignore b/.gitignore
index b2741131a5..5f733611de 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,6 +20,7 @@ rust-client/Cargo.toml
# Worktree-specific Claude Code settings (generated by scripts/worktree-env)
.claude/settings.local.json
+.claude/worktrees/
# Symlinked cache directories (for git worktrees)
backend/target
@@ -32,3 +33,4 @@ backend/chrome_profiler.json
.fast-check/
__pycache__/
.playwright-mcp/
+.codex
\ No newline at end of file
diff --git a/.mcp.json b/.mcp.json
index 8a587025cd..f6929bbd71 100644
--- a/.mcp.json
+++ b/.mcp.json
@@ -3,6 +3,16 @@
"svelte": {
"type": "http",
"url": "https://mcp.svelte.dev/mcp"
+ },
+ "playwright": {
+ "type": "stdio",
+ "command": "npx",
+ "args": ["-y", "@playwright/mcp@latest", "--browser", "chromium", "--headless"]
+ },
+ "playwright-headed": {
+ "type": "stdio",
+ "command": "npx",
+ "args": ["-y", "@playwright/mcp@latest", "--browser", "chromium"]
}
}
}
\ No newline at end of file
diff --git a/.webmux.yaml b/.webmux.yaml
index e00435465d..efd06f8ec2 100644
--- a/.webmux.yaml
+++ b/.webmux.yaml
@@ -47,6 +47,7 @@ profiles:
For this window specifically, backend is running on: ${BACKEND_PORT} and frontend is running on: ${FRONTEND_PORT}.
To connect to the database, use this connection string: ${DATABASE_URL}
Because we are running backend with cargo watch, to verify your changes, just check the logs in the backend pane. No need for cargo check.
+ For UI verification, use the Playwright MCP (`mcp__playwright__*`) — the `playwright` server is headless and works without a display. Navigate to http://localhost:${FRONTEND_PORT}, log in as admin@windmill.dev / changeme.
IMPORTANT: Read docs/autonomous-mode.md before starting any work.
panes:
- id: agent
@@ -76,6 +77,7 @@ profiles:
On this window specifically, frontend is running on: ${FRONTEND_PORT}.
To connect to the database, use this connection string: ${DATABASE_URL}
Because we are running frontend with npm run dev, to verify your changes, just check the logs in the frontend pane. No need for npm run build.
+ For UI verification, use the Playwright MCP (`mcp__playwright__*`) — the `playwright` server is headless and works without a display. Navigate to http://localhost:${FRONTEND_PORT}, log in as admin@windmill.dev / changeme.
IMPORTANT: Read docs/autonomous-mode.md before starting any work.
panes:
- id: agent
@@ -100,9 +102,55 @@ profiles:
integrations:
github:
+ autoRemoveOnMerge: true
linkedRepos:
- repo: windmill-labs/windmill-ee-private
alias: ee-private
dir: ../windmill-ee-private__worktrees
linear:
enabled: true
+ autoCreateWorktrees: true
+ watchTeams: [WIN,GIT]
+
+oneshot:
+ systemPrompt: |
+ You are running in webmux ONESHOT mode.
+
+ # No interactive user
+ There is NO interactive user — nobody is watching the chat or will respond
+ to questions, approvals, or status checks. Any message asking the user to
+ review, approve, confirm, take a look, or "let you know" is wasted output:
+ it will not be answered.
+
+ # Your job
+ Take the task to its real conclusion without pausing:
+ 1. Make the change.
+ 2. Validate it (run the relevant tests, typecheck, build, or quick
+ manual check). For UI changes, drive the running frontend with
+ the Playwright MCP (`mcp__playwright__*`, headless) and confirm
+ the change works end-to-end before moving on.
+ 3. Commit.
+ 4. Push.
+ 5. Open a pull request.
+ Only then are you done.
+
+ # Decisions
+ When something is ambiguous, pick the most reasonable default and proceed.
+ When you would normally ask "should I X or Y?", just pick one and continue
+ — note the choice in the PR description if it matters.
+
+ # PR readiness
+ Default to opening the PR as a draft. If you are highly confident in the
+ change — the scope is small and well-understood, validation passed
+ cleanly, and you would not change anything if a reviewer pushed back —
+ open the PR as ready-for-review directly (omit `--draft` when invoking
+ `gh pr create`, or call `gh pr ready ` after creation). Err on
+ the side of draft when validation was partial, the change touches
+ public APIs or shared infrastructure, or you made a non-obvious judgment
+ call.
+
+ # Ending your turn
+ Never end your turn with a question, a suggestion to "take a look", or a
+ request for approval. Stop only when the PR is open, or when you hit a
+ technical error you cannot recover from yourself (in which case clearly
+ state the blocker).
diff --git a/AGENTS.md b/AGENTS.md
index 825a033f94..5dda12ab70 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -15,9 +15,10 @@ Open-source platform for internal tools, workflows, API integrations, background
- **Enterprise**: `docs/enterprise.md` — EE file conventions and PR workflow
- **Backend patterns**: use the `rust-backend` skill when writing Rust code
- **Frontend patterns**: use the `svelte-frontend` skill when writing Svelte code. Do NOT edit svelte files unless you have read that skill.
-- **Code review**: use `/local-review` to review a PR for bugs and CLAUDE.md compliance
+- **Code review**: review the current PR or branch against the shared review policy in `REVIEW.md` (severity triage, public-surface checklist, AGENTS.md compliance, test-coverage assessment). The skill at `.agents/skills/local-review/SKILL.md` orchestrates it. All three CLIs auto-discover the same SKILL — Claude reads `.claude/skills/` (symlinked to the canonical `.agents/skills/` file), Codex and Pi read `.agents/skills/` directly. Invoke with `/local-review` in Claude Code, `$local-review` (or `/skills` selector) in Codex, or `pi --skill local-review` / `/skill:local-review` in Pi.
- **Domain guides**: `.claude/skills/native-trigger/` and `frontend/tutorial-system-guide.mdc`
- **Brand/UI guidelines**: `frontend/brand-guidelines.md`
+- **CLI commands**: when adding/modifying/removing a command, subcommand, option, or description in `cli/src/commands/`, run `python system_prompts/generate.py` to refresh `system_prompts/auto-generated/` and `cli/src/guidance/skills.gen.ts`. The CLI docs the agents use to operate `wmill` are derived from the source — stale generated files give agents the wrong flags.
## Dev Environment
@@ -28,6 +29,26 @@ Open-source platform for internal tools, workflows, API integrations, background
- **Instance settings**: navigate to `/#superadmin-settings`
- **Migrations**: use `cargo sqlx migrate add -r ` from `backend/` to create new migrations (never generate timestamps manually)
+## Verifying Frontend Changes
+
+After modifying frontend code, drive the running dev server with the **Playwright MCP** to verify the change in a real browser — don't claim a UI change works without exercising it.
+
+Two MCP servers are registered in `.mcp.json`:
+- `playwright` — headless Chromium, default for devboxes (no display required)
+- `playwright-headed` — windowed Chromium, when a display is available
+
+**One-time setup:** run `npx playwright install chromium` to download the browser binary (Playwright won't fetch it automatically on first use).
+
+Typical flow:
+1. Ensure backend (`cargo run`) and frontend (`REMOTE=http://localhost:8000 npm run dev`) are running
+2. `mcp__playwright__browser_navigate` to the relevant page (login at `admin@windmill.dev` / `changeme`)
+3. `mcp__playwright__browser_snapshot` to inspect the accessibility tree (preferred over screenshots for reading the DOM)
+4. `mcp__playwright__browser_click` / `browser_fill_form` / `browser_type` to interact
+5. `mcp__playwright__browser_take_screenshot` for visual confirmation
+6. `mcp__playwright__browser_console_messages` / `browser_network_requests` to surface errors
+
+If you cannot exercise a UI change (no dev server, etc.), say so explicitly rather than claiming success.
+
## Banned Patterns
### `$bindable(default_value)` on optional props
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 427eba2a5f..c725b0f69a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,469 @@
# Changelog
+## [1.713.1](https://github.com/windmill-labs/windmill/compare/v1.713.0...v1.713.1) (2026-06-01)
+
+
+### Bug Fixes
+
+* **api:** handle multi-version scripts when removing granular ACL ([#9388](https://github.com/windmill-labs/windmill/issues/9388)) ([9d9c503](https://github.com/windmill-labs/windmill/commit/9d9c5038ce8b0016320a670c434ef9063cb40441))
+
+## [1.713.0](https://github.com/windmill-labs/windmill/compare/v1.712.0...v1.713.0) (2026-05-31)
+
+
+### Features
+
+* **flows:** preserve step/subflow worker tags under a custom-tagged flow ([#9375](https://github.com/windmill-labs/windmill/issues/9375)) ([f0301b1](https://github.com/windmill-labs/windmill/commit/f0301b1605cee5fba4024803555333e6fa5c40ee))
+* **oauth:** support per-provider sandbox URLs ([#9358](https://github.com/windmill-labs/windmill/issues/9358)) ([2bf11dc](https://github.com/windmill-labs/windmill/commit/2bf11dcb15540c538ea2ac3cf70dcbe589060b4e))
+
+
+### Bug Fixes
+
+* **ai:** validate token_url for SSRF in OAuth credentials flow ([#9385](https://github.com/windmill-labs/windmill/issues/9385)) ([4b06881](https://github.com/windmill-labs/windmill/commit/4b06881918b76c5a411cc70b318e46efcc1393a7))
+* **api:** authorize and harden log-file reading endpoints ([#9368](https://github.com/windmill-labs/windmill/issues/9368)) ([bb90f4c](https://github.com/windmill-labs/windmill/commit/bb90f4ce83a0e60af219b11c12ab4fe1d13f47a4))
+* **apps:** make public apps opt into cross-origin isolation via wm_coep (GIT-884) ([#9374](https://github.com/windmill-labs/windmill/issues/9374)) ([2c0c2c4](https://github.com/windmill-labs/windmill/commit/2c0c2c467f163cd24c14c7be2db07af9cf2ce020))
+* **auth:** enforce monotonic privilege on user token lifecycle endpoints ([#9371](https://github.com/windmill-labs/windmill/issues/9371)) ([2ddf93d](https://github.com/windmill-labs/windmill/commit/2ddf93de96622b2a1b2b6f59398a7a1f59360efd))
+* batch encryption-key rotation into one git-sync job ([#9355](https://github.com/windmill-labs/windmill/issues/9355)) ([04a0897](https://github.com/windmill-labs/windmill/commit/04a08976aec4ba9b0516350316df303e9f96bfd3))
+* **cli:** preserve user drafts on sync push and permissioned-as ([#9381](https://github.com/windmill-labs/windmill/issues/9381)) ([b0c3b01](https://github.com/windmill-labs/windmill/commit/b0c3b01d31b0ab3a6566e1f5fec60e3e230cfadb))
+* **frontend:** sanitize user markdown to prevent stored XSS ([#9386](https://github.com/windmill-labs/windmill/issues/9386)) ([def01b8](https://github.com/windmill-labs/windmill/commit/def01b8ff6f331cc36ce02b947adc31c766042c4))
+* **security:** re-pin cached hub scripts to CVE-patched versions (+ HUB_BASE_URL override for cache mode) ([#9387](https://github.com/windmill-labs/windmill/issues/9387)) ([edf340c](https://github.com/windmill-labs/windmill/commit/edf340c4d4f18b16b142cb7deb67afa586f10946))
+
+## [1.712.0](https://github.com/windmill-labs/windmill/compare/v1.711.0...v1.712.0) (2026-05-28)
+
+
+### Features
+
+* add deepseek fim support ([#9365](https://github.com/windmill-labs/windmill/issues/9365)) ([2553fbf](https://github.com/windmill-labs/windmill/commit/2553fbfe31417bd985e7994eac695bf918f97ce2))
+* deploy raw apps from global chat ([#9349](https://github.com/windmill-labs/windmill/issues/9349)) ([dec58e6](https://github.com/windmill-labs/windmill/commit/dec58e6c4f55062b42a752c43c89ef05903e713a))
+* inject active editor into global chat ([#9361](https://github.com/windmill-labs/windmill/issues/9361)) ([9e7eaf3](https://github.com/windmill-labs/windmill/commit/9e7eaf36847ad3a004ec84e8b7d4784771b7b451))
+* **queue:** duration-weighted fairness admission ([#9334](https://github.com/windmill-labs/windmill/issues/9334)) ([045d120](https://github.com/windmill-labs/windmill/commit/045d12043e7c99830ef90bc0da798c94e2094711))
+* warn when custom instance db is shared across workspaces ([#9359](https://github.com/windmill-labs/windmill/issues/9359)) ([a9e5140](https://github.com/windmill-labs/windmill/commit/a9e514099585e5ee72df21bd551a223cceb20fb0))
+
+
+### Bug Fixes
+
+* **cli:** redact encryption_key diff in stdout by default ([#9347](https://github.com/windmill-labs/windmill/issues/9347)) ([88056f8](https://github.com/windmill-labs/windmill/commit/88056f8d4c91c1d14d85a08851ecf0bd97e2260d))
+* **cli:** stop re-prompting on wmill refresh prompts ([#9357](https://github.com/windmill-labs/windmill/issues/9357)) ([c2b5ba8](https://github.com/windmill-labs/windmill/commit/c2b5ba8871abbbcff6de69c90e2f09fee70586c1))
+* **frontend:** close other sidebar menus when hovering Help ([#9354](https://github.com/windmill-labs/windmill/issues/9354)) ([da882c5](https://github.com/windmill-labs/windmill/commit/da882c54b21e3eaf2c1d1abccd0996b243d96dce))
+* **frontend:** prevent duplicate asset node ids crashing flow graph ([#9367](https://github.com/windmill-labs/windmill/issues/9367)) ([9a659b6](https://github.com/windmill-labs/windmill/commit/9a659b636d713ee8fdfbdad41c58bb3d7c79e0d9))
+* **frontend:** prevent MultiSelect crash on undefined value ([#9364](https://github.com/windmill-labs/windmill/issues/9364)) ([aea0061](https://github.com/windmill-labs/windmill/commit/aea00611c41379be2afdad0eedd608c9537d03f7))
+* **git-sync:** publish fork branch on only_create_branch from the CLI ([#9366](https://github.com/windmill-labs/windmill/issues/9366)) ([2fdc51e](https://github.com/windmill-labs/windmill/commit/2fdc51e62985fc755884436130bdd58e294247c8))
+* infer script arg schema when deploying via AI chat ([#9356](https://github.com/windmill-labs/windmill/issues/9356)) ([4efc372](https://github.com/windmill-labs/windmill/commit/4efc37212a98571214aba135b0fbb10dc263fd4f))
+* **monitor:** cleanup stale server_heartbeat background_task_state rows ([#9338](https://github.com/windmill-labs/windmill/issues/9338)) ([59ab038](https://github.com/windmill-labs/windmill/commit/59ab038d7718d8a4c25efa5928f42e1393ebbf40))
+
+## [1.711.0](https://github.com/windmill-labs/windmill/compare/v1.710.1...v1.711.0) (2026-05-26)
+
+
+### Features
+
+* **cli:** add object-storage commands and flow test-step ([#9326](https://github.com/windmill-labs/windmill/issues/9326)) ([36f574f](https://github.com/windmill-labs/windmill/commit/36f574ff951198a4d40ee068a27d74c41ce32154))
+
+
+### Bug Fixes
+
+* **cli:** handle __flow suffix when deriving the flow's Windmill path ([#9333](https://github.com/windmill-labs/windmill/issues/9333)) ([6f77034](https://github.com/windmill-labs/windmill/commit/6f770346fb330997a836c39fba347df4c088a83c))
+* **queue:** duration-weighted workspace fairness signal ([#9329](https://github.com/windmill-labs/windmill/issues/9329)) ([42d2121](https://github.com/windmill-labs/windmill/commit/42d2121af925de50f549ecb72ffb5132f5c41079))
+
+## [1.710.1](https://github.com/windmill-labs/windmill/compare/v1.710.0...v1.710.1) (2026-05-26)
+
+
+### Bug Fixes
+
+* improve workspace fairness ([896add0](https://github.com/windmill-labs/windmill/commit/896add0350f4de31f5674d6be0907a582c5ec17e))
+
+## [1.710.0](https://github.com/windmill-labs/windmill/compare/v1.709.0...v1.710.0) (2026-05-26)
+
+
+### Features
+
+* **queue:** stochastic admission + EE availability of workspace fairness algorithm ([#9321](https://github.com/windmill-labs/windmill/issues/9321)) ([8bf7fd2](https://github.com/windmill-labs/windmill/commit/8bf7fd2c921c48861b71731a085b18ea8f72fb68))
+
+
+### Bug Fixes
+
+* **websocket-trigger:** honor HTTPS_PROXY/HTTP_PROXY/NO_PROXY ([#9324](https://github.com/windmill-labs/windmill/issues/9324)) ([6f36316](https://github.com/windmill-labs/windmill/commit/6f363163df9cd15f5af7d56cf34a01b70d236830))
+
+## [1.709.0](https://github.com/windmill-labs/windmill/compare/v1.708.0...v1.709.0) (2026-05-25)
+
+
+### Features
+
+* add copy button to Path component ([#9311](https://github.com/windmill-labs/windmill/issues/9311)) ([98bd5e7](https://github.com/windmill-labs/windmill/commit/98bd5e7f2a437b8b534028838b6ed0d7c59f7011))
+* **ai-chat:** align footer bar + DropdownV2 mode/autonomy selectors ([#9308](https://github.com/windmill-labs/windmill/issues/9308)) ([2f50e8b](https://github.com/windmill-labs/windmill/commit/2f50e8bab0b5ae9ae297c79abfe96df441f405e2))
+* **ai-chat:** expand chat question answers ([#9310](https://github.com/windmill-labs/windmill/issues/9310)) ([3f219ae](https://github.com/windmill-labs/windmill/commit/3f219aed98d93158aefce01bb51ed12dcb4711a1))
+* plug global chat drafts into userdraft ([#9291](https://github.com/windmill-labs/windmill/issues/9291)) ([1eef531](https://github.com/windmill-labs/windmill/commit/1eef53170b1b2afb75b9812e33787d1f28cf50dd))
+* **raw_apps:** surface UI Builder build errors over the preview pane ([#9316](https://github.com/windmill-labs/windmill/issues/9316)) ([90a196d](https://github.com/windmill-labs/windmill/commit/90a196d8d81993ffc2377d7088ab98f7b0f5ddcc))
+* **raw_apps:** tab-based editor surface with split-with-preview ([#9273](https://github.com/windmill-labs/windmill/issues/9273)) ([368e677](https://github.com/windmill-labs/windmill/commit/368e6774194a58058f28d1b4a42f8f4a7ec4ab63))
+* **service-accounts:** allow choosing role at creation time ([#9307](https://github.com/windmill-labs/windmill/issues/9307)) ([b125eca](https://github.com/windmill-labs/windmill/commit/b125eca7628b07c071bd102b161d389259fd6c62))
+
+
+### Bug Fixes
+
+* **auth:** filter resource/variable listings by token scope (WIN-1981) ([#9302](https://github.com/windmill-labs/windmill/issues/9302)) ([b5a0d46](https://github.com/windmill-labs/windmill/commit/b5a0d46695fdfe692d64573d1cfa06511e3b33f5))
+* **jobs:** authorization bypass in only_result job updates (WIN-1980) ([#9301](https://github.com/windmill-labs/windmill/issues/9301)) ([108a88a](https://github.com/windmill-labs/windmill/commit/108a88a1801548c8570d56aa3e1eb80246367bf4))
+
+## [1.708.0](https://github.com/windmill-labs/windmill/compare/v1.707.0...v1.708.0) (2026-05-24)
+
+
+### Features
+
+* **queue:** per-workspace fairness cap on the shared cloud worker pool ([#9303](https://github.com/windmill-labs/windmill/issues/9303)) ([de2e243](https://github.com/windmill-labs/windmill/commit/de2e243313ee34348675dec600cb412b475d1b4b))
+
+## [1.707.0](https://github.com/windmill-labs/windmill/compare/v1.706.1...v1.707.0) (2026-05-22)
+
+
+### Features
+
+* add wmill job rerun subcommand ([#9275](https://github.com/windmill-labs/windmill/issues/9275)) ([e0ffea2](https://github.com/windmill-labs/windmill/commit/e0ffea2deb5acf30815edd3669f4fc4c818b6e19))
+* **github-app:** hide cloud-only UI on self-managed + admin assignment UI ([#9299](https://github.com/windmill-labs/windmill/issues/9299)) ([dcee8cc](https://github.com/windmill-labs/windmill/commit/dcee8cc0d3dd71c3a12f1720e3ce4eb86cdacf4f))
+* **typescript-client:** add deleteS3File + optional workspace arg on S3 helpers ([#9300](https://github.com/windmill-labs/windmill/issues/9300)) ([daab561](https://github.com/windmill-labs/windmill/commit/daab561ec0763468d93e42e8f7f0796dc77be74d))
+
+
+### Bug Fixes
+
+* **auth:** tighten token-owner fallback for unscoped tokens (WIN-1978) ([#9293](https://github.com/windmill-labs/windmill/issues/9293)) ([7003998](https://github.com/windmill-labs/windmill/commit/7003998a575d76c272c6abd0789a1d1f7b722076))
+* **cli:** wmill sync pull updates wmill-lock.yaml for raw apps ([#9289](https://github.com/windmill-labs/windmill/issues/9289)) ([486e5f9](https://github.com/windmill-labs/windmill/commit/486e5f947b1649c17d32e3b214c50d4be701a4e8))
+* flow recording teardown crash + rename package to @windmill-labs/components ([#9288](https://github.com/windmill-labs/windmill/issues/9288)) ([13a2fae](https://github.com/windmill-labs/windmill/commit/13a2fae745ba4862006db5ee0811475c1d27fd1d))
+* **flows:** restore Variables and Resources in flow editor prop picker ([#9290](https://github.com/windmill-labs/windmill/issues/9290)) ([5566c7b](https://github.com/windmill-labs/windmill/commit/5566c7b3ff2d5a6b15cb9187aa15ce1c7245b3fb))
+* **ResourceEditor:** don't reset state when `selected` reverts to undefined ([#9295](https://github.com/windmill-labs/windmill/issues/9295)) ([1f2d2c1](https://github.com/windmill-labs/windmill/commit/1f2d2c11493db20b87615d41c21e5e1c35564739))
+* **secret-backend:** pass DB to Vault migrations + show failure details ([#9292](https://github.com/windmill-labs/windmill/issues/9292)) ([ace2291](https://github.com/windmill-labs/windmill/commit/ace22910c40585a6a2c9abd0c46f7e5e0214e78e))
+
+## [1.706.1](https://github.com/windmill-labs/windmill/compare/v1.706.0...v1.706.1) (2026-05-22)
+
+
+### Bug Fixes
+
+* fork compare visibility for non-admins and stale-token superadmins ([#9283](https://github.com/windmill-labs/windmill/issues/9283)) ([8272244](https://github.com/windmill-labs/windmill/commit/82722449e79da0b4b0ad4142aec7e7965e9ff236))
+* **git-sync:** bump to hub/28234 with stateless gpg.program wrapper (WIN-1974) ([#9282](https://github.com/windmill-labs/windmill/issues/9282)) ([89a2f07](https://github.com/windmill-labs/windmill/commit/89a2f07218818b95238b4a4484deab3138099672))
+* **nsjail:** gate unix-symlink test behind cfg(unix) for Windows build ([#9280](https://github.com/windmill-labs/windmill/issues/9280)) ([72e2c3a](https://github.com/windmill-labs/windmill/commit/72e2c3a6b3e0cb0f5bddf8291ae18bb8cf55ec28))
+
+## [1.706.0](https://github.com/windmill-labs/windmill/compare/v1.705.0...v1.706.0) (2026-05-21)
+
+
+### Features
+
+* add userdraft listing primitives ([#9268](https://github.com/windmill-labs/windmill/issues/9268)) ([d0ee697](https://github.com/windmill-labs/windmill/commit/d0ee697e8b8de58085ea0b2ecde1af2b2441428d))
+* add UV_PYTHON_INSTALL_MIRROR env and instance setting ([#9271](https://github.com/windmill-labs/windmill/issues/9271)) ([1169371](https://github.com/windmill-labs/windmill/commit/1169371d4885bdc18c76d03c6caae71f0e440235))
+* add yolo mode for ai chat tools ([#9258](https://github.com/windmill-labs/windmill/issues/9258)) ([ac26aa4](https://github.com/windmill-labs/windmill/commit/ac26aa4e4c7cc2d493f136b59738c0708803cc6d))
+* CLI datatable serve / psql ([#9267](https://github.com/windmill-labs/windmill/issues/9267)) ([28c8b5c](https://github.com/windmill-labs/windmill/commit/28c8b5c60fd46f961ae11b363b9be834fad6ee68))
+* **cli:** add `wmill init prompts` and custom override slot ([#9266](https://github.com/windmill-labs/windmill/issues/9266)) ([1ba8ed8](https://github.com/windmill-labs/windmill/commit/1ba8ed8abd827313ce0f7728d9f84357417206ee))
+* **nsjail:** optional disk-backed /tmp via instance setting ([#9272](https://github.com/windmill-labs/windmill/issues/9272)) ([b656dc6](https://github.com/windmill-labs/windmill/commit/b656dc6cdc8c50ef9740240447f119cceed18547))
+
+
+### Bug Fixes
+
+* **ai:** enforce RLS and scope check on user-supplied X-Resource-Path ([#9276](https://github.com/windmill-labs/windmill/issues/9276)) ([0692b97](https://github.com/windmill-labs/windmill/commit/0692b97c8a3818549d7050ea3e057e9cbf1ddb44))
+* **debugger:** add non-root user support to Dockerfile ([#9277](https://github.com/windmill-labs/windmill/issues/9277)) ([0bdb6a9](https://github.com/windmill-labs/windmill/commit/0bdb6a9d5d5fb28a27af1b6eda9fde7172308faf))
+* **indexer:** tell admins when ingress routes search to wrong pod ([#9274](https://github.com/windmill-labs/windmill/issues/9274)) ([d29a561](https://github.com/windmill-labs/windmill/commit/d29a5612fcd17eb4197468289e955a1209127cc1))
+
+## [1.705.0](https://github.com/windmill-labs/windmill/compare/v1.704.1...v1.705.0) (2026-05-20)
+
+
+### Features
+
+* add flow_user_state(key) to QuickJS input transform sandbox (WIN-1947) ([#9093](https://github.com/windmill-labs/windmill/issues/9093)) ([88c1493](https://github.com/windmill-labs/windmill/commit/88c149314576789f46feb5c7e1af3225b061f0c6))
+* add wmill protection-rules pull/push CLI commands ([#9240](https://github.com/windmill-labs/windmill/issues/9240)) ([01bad16](https://github.com/windmill-labs/windmill/commit/01bad16c0cc40fa64b2a72ccb8ded487c729cf35))
+* **chat:** visual redesign — input, streaming indicator, scroll polish ([#9232](https://github.com/windmill-labs/windmill/issues/9232)) ([31a0469](https://github.com/windmill-labs/windmill/commit/31a046973af960764ee4e153b68c020cbd4690ce))
+* **chat:** waiting-for-user indicator + scroll-to-latest polish ([#9252](https://github.com/windmill-labs/windmill/issues/9252)) ([7909878](https://github.com/windmill-labs/windmill/commit/790987831380611b5bd19a760b0a5433492d7796))
+* **cli:** add datatable and ducklake list/run commands ([#9257](https://github.com/windmill-labs/windmill/issues/9257)) ([1d04904](https://github.com/windmill-labs/windmill/commit/1d04904a47245062e50c2cc6362bbbb21a6987aa))
+* **debug:** show ghost breakpoint and tooltip on gutter hover ([#9150](https://github.com/windmill-labs/windmill/issues/9150)) ([271f0cb](https://github.com/windmill-labs/windmill/commit/271f0cbd087851fca86ed1530618ddbf728f13f3))
+* **editors:** responsive top-bars + collapsible raw-app sidebar ([#9237](https://github.com/windmill-labs/windmill/issues/9237)) ([b0ed270](https://github.com/windmill-labs/windmill/commit/b0ed27096d9e918e946cf8a8a04af8ff1892b50f))
+* export audit logs to a dedicated object store folder ([#9207](https://github.com/windmill-labs/windmill/issues/9207)) ([ba6fb70](https://github.com/windmill-labs/windmill/commit/ba6fb7021b5a720bff8e86b4741031902cf1c267))
+* **frontend:** new path component ([#9017](https://github.com/windmill-labs/windmill/issues/9017)) ([9c28bbf](https://github.com/windmill-labs/windmill/commit/9c28bbfd694a5047b4a8a9fe5cc2e54309f8f067))
+* **frontend:** sync home search bar state to URL ([#9256](https://github.com/windmill-labs/windmill/issues/9256)) ([31b7810](https://github.com/windmill-labs/windmill/commit/31b781000e62384af6b8e1ba0172e45ac6ab591f))
+* **git-sync:** hidden `sync git-deploy` owns wm_deploy branch + e2e regression tests ([#9230](https://github.com/windmill-labs/windmill/issues/9230)) ([07202fd](https://github.com/windmill-labs/windmill/commit/07202fd048c999c9d32f3feee94a08625050283d))
+* **indexer:** observability for unavailable search index (WIN-1956) ([#9239](https://github.com/windmill-labs/windmill/issues/9239)) ([285a787](https://github.com/windmill-labs/windmill/commit/285a78752a23aa467f9a82868d784599793d3a1f))
+* **nsjail:** make tmpfs size configurable via instance setting ([#9261](https://github.com/windmill-labs/windmill/issues/9261)) ([9111f89](https://github.com/windmill-labs/windmill/commit/9111f8908de82e9032a63711158dff9c6bca255b))
+* open ai chat path links in drawers ([#9220](https://github.com/windmill-labs/windmill/issues/9220)) ([f6fcdb5](https://github.com/windmill-labs/windmill/commit/f6fcdb5599c28b4890d6f775f657bfafeea1d380))
+* persistent in-editor drafts via UserDraft ([#9121](https://github.com/windmill-labs/windmill/issues/9121)) ([0f7dd86](https://github.com/windmill-labs/windmill/commit/0f7dd86e5c3a43bc62c4c0501efec34226b6e279))
+* resolve relative imports from local content in script/flow preview ([#9233](https://github.com/windmill-labs/windmill/issues/9233)) ([2a780ad](https://github.com/windmill-labs/windmill/commit/2a780ad87af69358f241536697f694612fe92d93))
+* **snowflake:** derive public key from private key when omitted (WIN-1959) ([#9251](https://github.com/windmill-labs/windmill/issues/9251)) ([aa12c66](https://github.com/windmill-labs/windmill/commit/aa12c66c25e68eefec22c213e8f228fd0699d8ce))
+* **vault:** optional KV secret path prefix setting (WIN-1960) ([#9249](https://github.com/windmill-labs/windmill/issues/9249)) ([d08f72b](https://github.com/windmill-labs/windmill/commit/d08f72b3e1ef194b5d656cafa15bc88e8b6ba731))
+
+
+### Bug Fixes
+
+* **autoscaling:** count custom worker groups by row, divide only native by NUM_WORKERS ([#9255](https://github.com/windmill-labs/windmill/issues/9255)) ([76d949e](https://github.com/windmill-labs/windmill/commit/76d949e7bc30eb8cadfdc52e031fcdf5ad97d2ed))
+* **autoscaling:** full-scale below min_workers on large backlog ([#9234](https://github.com/windmill-labs/windmill/issues/9234)) ([a4d59a8](https://github.com/windmill-labs/windmill/commit/a4d59a81dfb6fffbd185a3aa009eb90c160bf42b))
+* bound resource/variable interpolation recursion depth (WIN-1957) ([#9243](https://github.com/windmill-labs/windmill/issues/9243)) ([26f3cbe](https://github.com/windmill-labs/windmill/commit/26f3cbef259e643c6d79be893701eec70b7c0501))
+* cgroup-aware DuckDB memory_limit + allocator memory release ([#9245](https://github.com/windmill-labs/windmill/issues/9245)) ([0022112](https://github.com/windmill-labs/windmill/commit/00221128cbf0801a45bad40246e50beceaba0a7e))
+* collapse successful ai tool details ([#9265](https://github.com/windmill-labs/windmill/issues/9265)) ([413404a](https://github.com/windmill-labs/windmill/commit/413404a788bbe6b5c9df387a2db3000ffec74083))
+* early return should consider failure_module result ([#9241](https://github.com/windmill-labs/windmill/issues/9241)) ([2db1c0a](https://github.com/windmill-labs/windmill/commit/2db1c0a1fcfdcad94cae97dcffa090ffb91494f7))
+* enable jemalloc background_thread to prevent worker RSS growth ([#9236](https://github.com/windmill-labs/windmill/issues/9236)) ([a974ff6](https://github.com/windmill-labs/windmill/commit/a974ff68e00278ccaf441b0567cd46e2b5067fdd))
+* enforce auth guards on app component preview execution ([#9235](https://github.com/windmill-labs/windmill/issues/9235)) ([4b1bea8](https://github.com/windmill-labs/windmill/commit/4b1bea8aed51eb9e24940d89d984ce32f375ab0c))
+* **flows:** flag noLogs jobs and lazily resolve them in log panel ([#9099](https://github.com/windmill-labs/windmill/issues/9099)) ([740a35b](https://github.com/windmill-labs/windmill/commit/740a35bf7b20f0bd8cb94c3d703dd353f0711b0a))
+* **frontend:** flow progress bar for early-stop completion and error handler (WIN-1961) ([#9254](https://github.com/windmill-labs/windmill/issues/9254)) ([cc141ef](https://github.com/windmill-labs/windmill/commit/cc141effa3b1019f70f4b7230fe7ebc7017632a0))
+* **frontend:** open customer portal in popup synchronously to bypass Safari blocker ([#9242](https://github.com/windmill-labs/windmill/issues/9242)) ([f51b51a](https://github.com/windmill-labs/windmill/commit/f51b51a9a1aee5183fa597cf93ff14fbaddaffa9))
+* prevent undefined user flickering in multiplayer presence list ([#9231](https://github.com/windmill-labs/windmill/issues/9231)) ([8c1f6cc](https://github.com/windmill-labs/windmill/commit/8c1f6ccc5d22e657a83831eb5f37a9516cdec10a))
+* **s3:** sandbox stored XSS via download response headers ([#9263](https://github.com/windmill-labs/windmill/issues/9263)) ([bb78b1c](https://github.com/windmill-labs/windmill/commit/bb78b1c06de5b73b951691460f81a3a2ec6e7f80))
+* **saml:** preserve deep links from /a/[...path] across SAML round-trip ([#9259](https://github.com/windmill-labs/windmill/issues/9259)) ([78cf6c7](https://github.com/windmill-labs/windmill/commit/78cf6c7f8181ad431cffebd18c45a3a802b3a601))
+* scope VSCode webview clipboard paste to focused editor ([#9221](https://github.com/windmill-labs/windmill/issues/9221)) ([bd06282](https://github.com/windmill-labs/windmill/commit/bd062825a255da364c1590820ead65172e835d13))
+
+## [1.704.1](https://github.com/windmill-labs/windmill/compare/v1.704.0...v1.704.1) (2026-05-19)
+
+
+### Bug Fixes
+
+* fix git sync ([ff1deaa](https://github.com/windmill-labs/windmill/commit/ff1deaa7e2f3f1f650861c1e2a0f663e597d501c))
+* honor SAML RelayState to redirect to deep link after SSO login ([#9225](https://github.com/windmill-labs/windmill/issues/9225)) ([89306d7](https://github.com/windmill-labs/windmill/commit/89306d7dbc96d0c7dfe2c6025cefc2d72e4f224e))
+* revert git sync script bump ([0f54ecd](https://github.com/windmill-labs/windmill/commit/0f54ecd34cf1ac86ded9305bc84a044eb6a86e72))
+
+## [1.704.0](https://github.com/windmill-labs/windmill/compare/v1.703.3...v1.704.0) (2026-05-18)
+
+
+### Features
+
+* add global ask user question tool ([#9217](https://github.com/windmill-labs/windmill/issues/9217)) ([f965512](https://github.com/windmill-labs/windmill/commit/f965512c7a9aca32c252ca0cda7ec00ab08a38e0))
+* add global chat selected context ([#9216](https://github.com/windmill-labs/windmill/issues/9216)) ([49ebf6f](https://github.com/windmill-labs/windmill/commit/49ebf6f8ba0ea55ea7987f40ecdd32738241a3f2))
+* show job status in favicon on the run page ([#9206](https://github.com/windmill-labs/windmill/issues/9206)) ([2e05bdd](https://github.com/windmill-labs/windmill/commit/2e05bdd73a664ddeec513653ab74e8c696ea1cfd))
+
+
+### Bug Fixes
+
+* don't fail flow on AlreadyCompleted after zombie restart ([#9214](https://github.com/windmill-labs/windmill/issues/9214)) ([8b7f7b3](https://github.com/windmill-labs/windmill/commit/8b7f7b37bdb91449cbd868bd3ee33a0ccbaf288f))
+* **git-sync:** bump default sync script to hub/28229 for extra_perms support ([#9223](https://github.com/windmill-labs/windmill/issues/9223)) ([0538412](https://github.com/windmill-labs/windmill/commit/0538412f1c370981be1915d8c724879d2c54fb83))
+* preserve ai reasoning content ([#9208](https://github.com/windmill-labs/windmill/issues/9208)) ([fec4008](https://github.com/windmill-labs/windmill/commit/fec40086961174fea25b4e1f796991152b84b211))
+* reject path traversal in MCP endpoint path parameters ([#9211](https://github.com/windmill-labs/windmill/issues/9211)) ([ad5ec29](https://github.com/windmill-labs/windmill/commit/ad5ec293b5a189135faea21e0d9c93637b77670f))
+* resolve absolute-path imports in monaco ts editor ([#9213](https://github.com/windmill-labs/windmill/issues/9213)) ([156eb0b](https://github.com/windmill-labs/windmill/commit/156eb0b045171e8d6990af9eeab752071bf7097b))
+
+## [1.703.3](https://github.com/windmill-labs/windmill/compare/v1.703.2...v1.703.3) (2026-05-18)
+
+
+### Bug Fixes
+
+* constrain unauthenticated get_public_resource to app_theme resources ([#9203](https://github.com/windmill-labs/windmill/issues/9203)) ([24eedef](https://github.com/windmill-labs/windmill/commit/24eedef918376d9d401335b6fada577916f8cc0e))
+* enforce folder ACL on flow run-by-version routes ([#9202](https://github.com/windmill-labs/windmill/issues/9202)) ([ab11c77](https://github.com/windmill-labs/windmill/commit/ab11c7747a9076e8121fcea6eafb8e88079ac987))
+* enforce jobs:run scope on job preview and inline endpoints ([#9198](https://github.com/windmill-labs/windmill/issues/9198)) ([664edcd](https://github.com/windmill-labs/windmill/commit/664edcdfb746f6c8513e2b487383b5d9ab9f5434))
+* **mcp:** validate oauth dynamic client registration redirect_uris ([#9197](https://github.com/windmill-labs/windmill/issues/9197)) ([8bc2295](https://github.com/windmill-labs/windmill/commit/8bc2295b94df159a7c8630cdbe02953b8b7c13a1))
+* validate entrypoint override to prevent worker code injection (GHSA-wxjq-w5pj-jqhx) ([#9204](https://github.com/windmill-labs/windmill/issues/9204)) ([bd05bca](https://github.com/windmill-labs/windmill/commit/bd05bcadde06b65fc4b732f576d89aae908b5a3f))
+
+## [1.703.2](https://github.com/windmill-labs/windmill/compare/v1.703.1...v1.703.2) (2026-05-17)
+
+
+### Bug Fixes
+
+* prevent cross-tenant DNS poisoning via writable /etc in nsjail ([#9194](https://github.com/windmill-labs/windmill/issues/9194)) ([f8467f3](https://github.com/windmill-labs/windmill/commit/f8467f38c8a053117ce62f96684cfb15ef792f08))
+
+## [1.703.1](https://github.com/windmill-labs/windmill/compare/v1.703.0...v1.703.1) (2026-05-16)
+
+
+### Bug Fixes
+
+* actionable error when a custom_path is taken by an app in another workspace ([#9190](https://github.com/windmill-labs/windmill/issues/9190)) ([dfeed9c](https://github.com/windmill-labs/windmill/commit/dfeed9c5c2e39bf3e10eea4f69ea140ee9e7832f))
+* atomic bundle cache writes to prevent parallel cold-load race ([#9186](https://github.com/windmill-labs/windmill/issues/9186)) ([81b5736](https://github.com/windmill-labs/windmill/commit/81b573610692b386e4861ef989fa7698b53fc861))
+* detect S3 assets passed as SDK object arg in ts parser ([#9181](https://github.com/windmill-labs/windmill/issues/9181)) ([6a334e9](https://github.com/windmill-labs/windmill/commit/6a334e9a07a7d0cffabde48be75263b0844d586c))
+* don't show ALLOW_PRIVATE_AI_BASE_URLS hint for malformed AI base URLs ([#9188](https://github.com/windmill-labs/windmill/issues/9188)) ([4e25954](https://github.com/windmill-labs/windmill/commit/4e259547225e13e5b51a166a84cdbbbfa35c3264))
+* reset parent_hash in auto_parent when all versions at path are archived ([#9172](https://github.com/windmill-labs/windmill/issues/9172)) ([52960ca](https://github.com/windmill-labs/windmill/commit/52960ca30ab9c019186a28b3ab054a1dfe72f451))
+
+## [1.703.0](https://github.com/windmill-labs/windmill/compare/v1.702.1...v1.703.0) (2026-05-15)
+
+
+### Features
+
+* **otel-tracing-proxy:** configurable tracing MITM NO_PROXY hosts ([#9169](https://github.com/windmill-labs/windmill/issues/9169)) ([d48d61c](https://github.com/windmill-labs/windmill/commit/d48d61cc79114f0b36736306d4015789be10c1f4))
+
+
+### Bug Fixes
+
+* aggregate wait time should target the true root job, not flow_innermost_root_job ([#9177](https://github.com/windmill-labs/windmill/issues/9177)) ([e181931](https://github.com/windmill-labs/windmill/commit/e1819313e15766007c959497a84fae5f5c78a46b))
+* apply pip_local_dependencies filtering to deployed scripts with populated lockfiles ([#9178](https://github.com/windmill-labs/windmill/issues/9178)) ([69b3141](https://github.com/windmill-labs/windmill/commit/69b3141e0370b95f2e13987503480d341608dbdf))
+* never mark failure/trigger/approval scripts as auto_kind=lib ([#9168](https://github.com/windmill-labs/windmill/issues/9168)) ([f414ffc](https://github.com/windmill-labs/windmill/commit/f414ffc4849cf4b92fcd5ca9611ecd246e59a7bd))
+
+## [1.702.1](https://github.com/windmill-labs/windmill/compare/v1.702.0...v1.702.1) (2026-05-14)
+
+
+### Bug Fixes
+
+* **nativets:** pass tracing-enabled OtelConfig to deno_telemetry::init ([#9163](https://github.com/windmill-labs/windmill/issues/9163)) ([bf99283](https://github.com/windmill-labs/windmill/commit/bf99283c3333bcdbc7679f4aea04ba29e41a48a5))
+
+## [1.702.0](https://github.com/windmill-labs/windmill/compare/v1.701.0...v1.702.0) (2026-05-14)
+
+
+### Features
+
+* **git-sync:** sync extra_perms for flows/scripts/apps ([#9162](https://github.com/windmill-labs/windmill/issues/9162)) ([5e909b2](https://github.com/windmill-labs/windmill/commit/5e909b2b4f2819f19deaf06d9e78e6458b324683))
+* include service accounts in instance settings users list ([#9157](https://github.com/windmill-labs/windmill/issues/9157)) ([e5286f4](https://github.com/windmill-labs/windmill/commit/e5286f46074cf2893e6ccd26175f929f16011c8f))
+
+
+### Bug Fixes
+
+* **mcp:** sanitize and enrich nested resource schemas ([#9158](https://github.com/windmill-labs/windmill/issues/9158)) ([d870edc](https://github.com/windmill-labs/windmill/commit/d870edc959481a06c894b4eda5e2be1a0269d7d0))
+
+## [1.701.0](https://github.com/windmill-labs/windmill/compare/v1.700.2...v1.701.0) (2026-05-13)
+
+
+### Features
+
+* **frontend:** unified EditorHeader with file picker for flow/script/app editors ([#9047](https://github.com/windmill-labs/windmill/issues/9047)) ([d0f23cc](https://github.com/windmill-labs/windmill/commit/d0f23cc5238b025208c61e983701894de28536d5))
+* read-only flag on API tokens ([#9144](https://github.com/windmill-labs/windmill/issues/9144)) ([d666e84](https://github.com/windmill-labs/windmill/commit/d666e8431cdbf14d9373d9ef625b5aafc50ac50a))
+
+
+### Bug Fixes
+
+* align script path existence check with deploy logic; hide Delete for non-admin ([#9152](https://github.com/windmill-labs/windmill/issues/9152)) ([c509206](https://github.com/windmill-labs/windmill/commit/c5092069cbeda2c4c18bea80dd629c7c087b30bf))
+* Allow devops role to use all_workspaces runs filter in admins workspace ([#9153](https://github.com/windmill-labs/windmill/issues/9153)) ([110bef0](https://github.com/windmill-labs/windmill/commit/110bef0a6e76615c7b371c5c0f5bc1f4e7a73a64))
+* **bun:** pass --preserve-symlinks on unbundled execution ([#9147](https://github.com/windmill-labs/windmill/issues/9147)) ([4d0f2c2](https://github.com/windmill-labs/windmill/commit/4d0f2c26a116a0f8a89a64231dc824eabda0a8c3))
+* **cli:** prevent !inline-corruption in flow push/pull ([#9142](https://github.com/windmill-labs/windmill/issues/9142)) ([79c5b7b](https://github.com/windmill-labs/windmill/commit/79c5b7b8b7676b0a06fa6480dd04b7105d39d250))
+* **operator:** refresh IAM RDS / Entra ID tokens in operator process ([#9141](https://github.com/windmill-labs/windmill/issues/9141)) ([7ebb081](https://github.com/windmill-labs/windmill/commit/7ebb08133cd4027bc00bacc4a0fc5865cd5709ec))
+* **python:** preserve strings containing Infinity/NaN in result JSON ([#9149](https://github.com/windmill-labs/windmill/issues/9149)) ([33bf01b](https://github.com/windmill-labs/windmill/commit/33bf01b627c8ea430c03dfc27a97a8f2d770582f))
+* scope promotion-mode debounce key per repo ([#9145](https://github.com/windmill-labs/windmill/issues/9145)) ([2ec1863](https://github.com/windmill-labs/windmill/commit/2ec1863340e759bba3408dbc4f41b16912b959ea))
+* send flow push-loop ping outside transaction so zombie monitor sees it ([#9136](https://github.com/windmill-labs/windmill/issues/9136)) ([818cb31](https://github.com/windmill-labs/windmill/commit/818cb31fbc731fa5c70ddf5942bb37bc4bc56e4d))
+
+
+### Performance Improvements
+
+* **dynselect:** only retrigger when helper args actually change ([#9148](https://github.com/windmill-labs/windmill/issues/9148)) ([dd19e52](https://github.com/windmill-labs/windmill/commit/dd19e52a84fb9a9f48e3ad061b084841c2ee7464))
+
+## [1.700.2](https://github.com/windmill-labs/windmill/compare/v1.700.1...v1.700.2) (2026-05-12)
+
+
+### Bug Fixes
+
+* preserve explicit nulls for typed fields in bulk instance config ([#9123](https://github.com/windmill-labs/windmill/issues/9123)) ([cab0000](https://github.com/windmill-labs/windmill/commit/cab0000f3a5e9a0b201a85da1a01b1f82df8a316))
+* preserve negative integers in Bedrock tool schema conversion ([#9116](https://github.com/windmill-labs/windmill/issues/9116)) ([01e21c7](https://github.com/windmill-labs/windmill/commit/01e21c7f913eaf7dffc3d6a31501418ff2104c8b))
+
+## [1.700.1](https://github.com/windmill-labs/windmill/compare/v1.700.0...v1.700.1) (2026-05-11)
+
+
+### Bug Fixes
+
+* CE build broken by enterprise-gated compute_instance_hash ([#9113](https://github.com/windmill-labs/windmill/issues/9113)) ([cd65de4](https://github.com/windmill-labs/windmill/commit/cd65de49285ff60abdd94c883180ded65609f382))
+
+## [1.700.0](https://github.com/windmill-labs/windmill/compare/v1.699.0...v1.700.0) (2026-05-11)
+
+
+### Features
+
+* **cli:** auto-infer args for `wmill app push` ([#9091](https://github.com/windmill-labs/windmill/issues/9091)) ([43b1800](https://github.com/windmill-labs/windmill/commit/43b18006f32fd5db54bbf8ae7ff0e0b314a517e5))
+* **forks:** prompt to delete forked children when deleting a fork ([#9097](https://github.com/windmill-labs/windmill/issues/9097)) ([e43a958](https://github.com/windmill-labs/windmill/commit/e43a958c5c6ae01a1fbecf3db63c6541a245be62))
+* **operators:** allow operators to access assets page ([#9095](https://github.com/windmill-labs/windmill/issues/9095)) ([20ecd90](https://github.com/windmill-labs/windmill/commit/20ecd904e7060c3cf90f2605740bb349b2a3e6ed))
+* **vault:** configurable JWT auth mount path and setup-doc fixes ([#9100](https://github.com/windmill-labs/windmill/issues/9100)) ([f8ba084](https://github.com/windmill-labs/windmill/commit/f8ba0840d74572c880cf458938365b3ec808c6fb))
+
+
+### Bug Fixes
+
+* add Input, Result, Trigger to reserved flow step IDs ([#9109](https://github.com/windmill-labs/windmill/issues/9109)) ([9f79a86](https://github.com/windmill-labs/windmill/commit/9f79a86a686708f66ccc512d4f132cb9a00397a7)), closes [#7139](https://github.com/windmill-labs/windmill/issues/7139)
+* **frontend:** mark Path dirty when folder picker changes selection ([#9096](https://github.com/windmill-labs/windmill/issues/9096)) ([23bb1b5](https://github.com/windmill-labs/windmill/commit/23bb1b541e78846d5978153fd8d9bb4f01cec72b))
+* mask oauth client secret in instance settings ([#9112](https://github.com/windmill-labs/windmill/issues/9112)) ([ac3c155](https://github.com/windmill-labs/windmill/commit/ac3c155541eb5ca20d65c38ad13dca6c10a572c9))
+* populate raw_code for flowscript and appscript runs ([#9104](https://github.com/windmill-labs/windmill/issues/9104)) ([05172ac](https://github.com/windmill-labs/windmill/commit/05172ac3bdfc3472da5e9d8a825cdd479ba9e375))
+
+
+### Performance Improvements
+
+* lazy-load script editor history and hit partial index ([#9107](https://github.com/windmill-labs/windmill/issues/9107)) ([03e8bc8](https://github.com/windmill-labs/windmill/commit/03e8bc8c14258355d7d695333c1588807fbf8cd6))
+
+## [1.699.0](https://github.com/windmill-labs/windmill/compare/v1.698.0...v1.699.0) (2026-05-08)
+
+
+### Features
+
+* parse windmill_failure field to tag run as failure ([#9073](https://github.com/windmill-labs/windmill/issues/9073)) ([dd53202](https://github.com/windmill-labs/windmill/commit/dd5320205f200dd058db2ff7d44d5c4bbcf25ec9))
+
+
+### Bug Fixes
+
+* **cli:** bump svelte version in `wmill app new` template ([#9084](https://github.com/windmill-labs/windmill/issues/9084)) ([4b4aa0e](https://github.com/windmill-labs/windmill/commit/4b4aa0e303f9c47c4f931511977107f42f93abc3))
+* **flows:** populate error handler input args from failure picker ([#9087](https://github.com/windmill-labs/windmill/issues/9087)) ([f37d360](https://github.com/windmill-labs/windmill/commit/f37d3606446d23f8b11a94ea1ce5f5d4836fae17))
+* hide _ENTRYPOINT_OVERRIDE jobs from script/flow history panel ([#9088](https://github.com/windmill-labs/windmill/issues/9088)) ([935c666](https://github.com/windmill-labs/windmill/commit/935c666d50ef30d89a3669c76094af7506fbb448))
+* **native-triggers:** serialize Google channel renewal across replicas ([#9060](https://github.com/windmill-labs/windmill/issues/9060)) ([ee3d82f](https://github.com/windmill-labs/windmill/commit/ee3d82f01f52d835218f544dad6de9b7c3184fbb))
+* **python:** verify wheel RECORD on cache pull/install, finalize piptar ([#9090](https://github.com/windmill-labs/windmill/issues/9090)) ([98ff146](https://github.com/windmill-labs/windmill/commit/98ff146cfabf45418c95c027ad6d07b08069cfcd))
+* reject root-rooted paths in ansible playbook validator on windows ([#9081](https://github.com/windmill-labs/windmill/issues/9081)) ([d37277d](https://github.com/windmill-labs/windmill/commit/d37277d2341c83faf72efa0035cbf70e2cfbd596))
+
+
+### Performance Improvements
+
+* **flows:** gate flow_env resolve on expr text and share cache with handle_flow ([#9085](https://github.com/windmill-labs/windmill/issues/9085)) ([23af6c2](https://github.com/windmill-labs/windmill/commit/23af6c2ea31265a1898d0632e72cd2fd826e4044))
+
+## [1.698.0](https://github.com/windmill-labs/windmill/compare/v1.697.0...v1.698.0) (2026-05-08)
+
+
+### Features
+
+* **cli:** add --parallel flag to generate-metadata ([#9074](https://github.com/windmill-labs/windmill/issues/9074)) ([bc527fd](https://github.com/windmill-labs/windmill/commit/bc527fd929577ac57d4e24196069ed236b702d71))
+
+
+### Bug Fixes
+
+* **cli-tests:** stabilize flow lock-gen race + Windows path ([#9080](https://github.com/windmill-labs/windmill/issues/9080)) ([1c56148](https://github.com/windmill-labs/windmill/commit/1c56148714861aafc4f489916c71aa4674e938c0))
+* **cli:** forward HEADERS env var on every backend fetch call ([#9075](https://github.com/windmill-labs/windmill/issues/9075)) ([d647686](https://github.com/windmill-labs/windmill/commit/d6476862b30692e450cceda09c58d47964f87d32))
+
+
+### Performance Improvements
+
+* **flows:** cache resolved flow_env per flow execution ([#9079](https://github.com/windmill-labs/windmill/issues/9079)) ([e1a7c75](https://github.com/windmill-labs/windmill/commit/e1a7c75e192b72b3b0d854c1901653e0b9386bf2))
+* **flows:** skip flow_env DB+transform work when no resolution is needed ([#9078](https://github.com/windmill-labs/windmill/issues/9078)) ([2067e07](https://github.com/windmill-labs/windmill/commit/2067e0719fd1fd1b899b015badec0f222c054e66))
+
+## [1.697.0](https://github.com/windmill-labs/windmill/compare/v1.696.2...v1.697.0) (2026-05-07)
+
+
+### Features
+
+* add workspace-specific flag for resources and variables ([#8836](https://github.com/windmill-labs/windmill/issues/8836)) ([4427a3d](https://github.com/windmill-labs/windmill/commit/4427a3d37f6e1edef93082322ee346077e0c1ff6))
+* **forks:** handle triggers and schedules in wmill workspace merge ([#9023](https://github.com/windmill-labs/windmill/issues/9023)) ([9de38f9](https://github.com/windmill-labs/windmill/commit/9de38f9a09d2a5759de98849c78f5470bffef94b))
+* **kafka-trigger:** OAUTHBEARER + SASL_SSL support ([#9054](https://github.com/windmill-labs/windmill/issues/9054)) ([80475f0](https://github.com/windmill-labs/windmill/commit/80475f011bf8f7ffbd5afa50fb5df37cdac0825d))
+* **secret-backend:** add Workload Identity Federation for Azure Key Vault ([#9061](https://github.com/windmill-labs/windmill/issues/9061)) ([0c203e8](https://github.com/windmill-labs/windmill/commit/0c203e8cf1cd23fa61675c5691c2649bf0664de0))
+
+
+### Bug Fixes
+
+* **cli:** stable auto-numbered inline-script names in app pull ([#9071](https://github.com/windmill-labs/windmill/issues/9071)) ([8c67e5f](https://github.com/windmill-labs/windmill/commit/8c67e5fdb78b4f17b8866e97bc04168519a65021))
+* **concurrency:** two-phase admit to skip FOR UPDATE on over-limit pulls ([#9064](https://github.com/windmill-labs/windmill/issues/9064)) ([153c4e6](https://github.com/windmill-labs/windmill/commit/153c4e6aff9344a9d6059737c8614c25c9b4443a))
+* handle singlestepflow zombies and stop filtering them from runs page ([#9055](https://github.com/windmill-labs/windmill/issues/9055)) ([e74f06c](https://github.com/windmill-labs/windmill/commit/e74f06cb561d2a2652477e2b6d2ea645e9c77964))
+* Log viewer fixed top bar ([#9070](https://github.com/windmill-labs/windmill/issues/9070)) ([23e081b](https://github.com/windmill-labs/windmill/commit/23e081b078df961d9c42ca12472e3daa7479e290))
+* scope dev server CSS reset to a layer so Tailwind utilities win ([#9069](https://github.com/windmill-labs/windmill/issues/9069)) ([6df79a4](https://github.com/windmill-labs/windmill/commit/6df79a457269a02747d8ec37ba5e97deec7a3e42))
+
+## [1.696.2](https://github.com/windmill-labs/windmill/compare/v1.696.1...v1.696.2) (2026-05-06)
+
+
+### Bug Fixes
+
+* bubble handle_flow chaining errors to parent flow ([#9058](https://github.com/windmill-labs/windmill/issues/9058)) ([5bca03e](https://github.com/windmill-labs/windmill/commit/5bca03eacdbf147268d8ad9079e9ec45b2819af0))
+* **bun:** make hub script cache resilient to malformed lockfiles ([#9063](https://github.com/windmill-labs/windmill/issues/9063)) ([c6f1c5e](https://github.com/windmill-labs/windmill/commit/c6f1c5e623716df703dd6b37be9981dbe5742550))
+* **cli:** detect upstream auth-gateway HTML responses and add poll heartbeat ([#9065](https://github.com/windmill-labs/windmill/issues/9065)) ([628ab56](https://github.com/windmill-labs/windmill/commit/628ab5692e1825001eac0aaa92638c0067a508ea))
+* **queue:** cap worker pull loop at 10 to avoid DB storm ([#9062](https://github.com/windmill-labs/windmill/issues/9062)) ([e3cc258](https://github.com/windmill-labs/windmill/commit/e3cc2584555d532d91aa888cc52af34a16277036))
+
+## [1.696.1](https://github.com/windmill-labs/windmill/compare/v1.696.0...v1.696.1) (2026-05-06)
+
+
+### Bug Fixes
+
+* **bun:** propagate non-zero exit from generate_bun_bundle on no-DB path ([#9051](https://github.com/windmill-labs/windmill/issues/9051)) ([eebaab9](https://github.com/windmill-labs/windmill/commit/eebaab9c87f975b70049e118a08665fc21653b13))
+* **workspaces:** validate fork id as a git branch name component ([#9049](https://github.com/windmill-labs/windmill/issues/9049)) ([f4553e8](https://github.com/windmill-labs/windmill/commit/f4553e8e7919b115a4239a62ee4587347cc82bb8))
+
+## [1.696.0](https://github.com/windmill-labs/windmill/compare/v1.695.0...v1.696.0) (2026-05-05)
+
+
+### Features
+
+* add ai chat resource action buttons ([#9016](https://github.com/windmill-labs/windmill/issues/9016)) ([502a029](https://github.com/windmill-labs/windmill/commit/502a02998685308e82fab95bae7d3c14efd77d6a))
+* add wac ai context for frontend chat ([#9021](https://github.com/windmill-labs/windmill/issues/9021)) ([0d0557f](https://github.com/windmill-labs/windmill/commit/0d0557fc9dc5addee887911ddfc0fd08a09bc92e))
+* **cli:** add --as-superadmin flag to workspace list-remote ([#9043](https://github.com/windmill-labs/windmill/issues/9043)) ([66c9063](https://github.com/windmill-labs/windmill/commit/66c90639191a77eb4f19da092167384565edb9b3))
+
+
+### Bug Fixes
+
+* **cli:** resolve cross-folder relative imports during lockgen on fresh DB ([#9048](https://github.com/windmill-labs/windmill/issues/9048)) ([40dbab5](https://github.com/windmill-labs/windmill/commit/40dbab531e5166b894f3f94b0d72b2ac456c0097))
+* **flows:** inherit flow_env in sub-flow predicates ([#9042](https://github.com/windmill-labs/windmill/issues/9042)) ([6e5a21a](https://github.com/windmill-labs/windmill/commit/6e5a21a9c7b5db77d325b5916a9ab8799a2eb6e7))
+* navigate home arrows ([#9024](https://github.com/windmill-labs/windmill/issues/9024)) ([c1e52ea](https://github.com/windmill-labs/windmill/commit/c1e52eab09794746bea7dc9adb94552641f87d5b))
+* open job detail header path links in a new tab ([#9039](https://github.com/windmill-labs/windmill/issues/9039)) ([fe68c06](https://github.com/windmill-labs/windmill/commit/fe68c066004d860088e09be32e7ff2e7438f78c4))
+* **rust-client:** re-export models module from wmill crate ([#9038](https://github.com/windmill-labs/windmill/issues/9038)) ([ca6efbf](https://github.com/windmill-labs/windmill/commit/ca6efbff74e7d7e85174b1d8394af79eda7d6535))
+* **windmill-utils-internal:** move config to subpath export ([#9045](https://github.com/windmill-labs/windmill/issues/9045)) ([b86f896](https://github.com/windmill-labs/windmill/commit/b86f8960fcd8a66bc6849638178ef45ac49e06f1))
+
+## [1.695.0](https://github.com/windmill-labs/windmill/compare/v1.694.0...v1.695.0) (2026-05-04)
+
+
+### Features
+
+* add separate filter searchbar for resource types tab ([#9019](https://github.com/windmill-labs/windmill/issues/9019)) ([f1fd245](https://github.com/windmill-labs/windmill/commit/f1fd245073d6bf97a6a6c64e64d545618cccc432))
+
+
+### Bug Fixes
+
+* **autoscaling:** consider dedicated workers in scale decisions ([#9020](https://github.com/windmill-labs/windmill/issues/9020)) ([42be1d4](https://github.com/windmill-labs/windmill/commit/42be1d46a632c23830f97995e9ab1b52a1ed5d3d))
+* bind MySQL table listing to configured database name ([#9007](https://github.com/windmill-labs/windmill/issues/9007)) ([44fad13](https://github.com/windmill-labs/windmill/commit/44fad139fe2076f5faa62de31aaeeb217466b25e))
+* **flows:** don't bubble error when continue_on_error is on the last step ([#9029](https://github.com/windmill-labs/windmill/issues/9029)) ([192866d](https://github.com/windmill-labs/windmill/commit/192866d5197c74ec930d6fe7bf9234fac76763f4))
+* **forks:** strip mode/enabled from merge-UI deploy payload ([#9008](https://github.com/windmill-labs/windmill/issues/9008)) ([da95588](https://github.com/windmill-labs/windmill/commit/da95588b253e8bb2a06b9792674b4d691384f55a))
+* stop sequential whileloop on iteration failure ([#9028](https://github.com/windmill-labs/windmill/issues/9028)) ([1be62ea](https://github.com/windmill-labs/windmill/commit/1be62ea926872882ddbd4c8ce81502d6e341b8c1))
+
## [1.694.0](https://github.com/windmill-labs/windmill/compare/v1.693.4...v1.694.0) (2026-05-01)
diff --git a/Dockerfile b/Dockerfile
index e11cf9cecd..9062a4d9d8 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -66,6 +66,7 @@ RUN npm ci
COPY frontend .
RUN mkdir /backend
COPY /backend/windmill-api/openapi.yaml /backend/windmill-api/openapi.yaml
+COPY /backend/oauth_connect.json /backend/oauth_connect.json
COPY /openflow.openapi.yaml /openflow.openapi.yaml
COPY /backend/windmill-api/build_openapi.sh /backend/windmill-api/build_openapi.sh
COPY /system_prompts/auto-generated /system_prompts/auto-generated
diff --git a/REVIEW.md b/REVIEW.md
new file mode 100644
index 0000000000..21f5e50f26
--- /dev/null
+++ b/REVIEW.md
@@ -0,0 +1,69 @@
+# Pull request review — shared policy
+
+You are reviewing a GitHub pull request for this repository. Apply this policy alongside your tool's output requirements.
+
+## Read the project rules first
+
+- Read `AGENTS.md` (repo root) and any `AGENTS.md` in directories touched by the diff before reviewing — they are the canonical contributor guide.
+- Quote the exact rule from `AGENTS.md` when flagging a violation.
+
+## Verdict (first line of the review)
+
+Start every review with a single verdict line, before any other section (the only thing that may appear above the verdict is the optional `cc @` ping described in "Pinging the author" below). Pick exactly one:
+
+- **Good to merge** — no blocking issues and no nits worth surfacing.
+- **Mergeable, but should ideally address nits: ** — no blockers, but P2 findings that are worth a look. The list must name each nit briefly (e.g. "doc/code mismatch in `foo.rs`, half-finished `pub fn bar`").
+- **Should address issues before merging: ** — at least one P0 or P1 finding. The list must name each blocking issue briefly (e.g. "missing auth check on new `/api/x` handler, SQL injection in `build_query`").
+
+The names in the list must match findings detailed later in the review. If you list a nit or issue here, it must appear with full context in the body. Do not invent items that aren't in the body, and do not bury blockers in the body without surfacing them in the verdict.
+
+## Pinging the author
+
+If the prompt context provides a `PR AUTHOR` (GitHub login) and the verdict is NOT "Good to merge" (i.e. it is "Mergeable, but should ideally address nits: ..." or "Should address issues before merging: ..."), prepend a single line `cc @` to the top-level review comment, above the verdict line. This pings the author so they get a notification that there are items to address. Skip the ping entirely when the verdict is "Good to merge" — there is nothing for the author to act on. Do not add the ping to inline comments; the top-level summary comment is the only place it belongs.
+
+## Review policy
+
+- Only report issues you are confident are real and introduced by this pull request.
+- Focus on bugs, security problems, performance, and clear `AGENTS.md` violations.
+- Do not report style nits, speculative concerns, pre-existing issues, or anything a normal linter / typechecker would obviously catch.
+- Self-validate each finding before posting: "is this definitely a real issue?" If uncertain, discard it.
+- Read additional files only when the diff is not enough to validate a finding.
+- Do not modify any files.
+
+## Severity triage
+
+Tag each finding with a severity. Always report P0 and P1. Report P2 only when the diff invites it (a new `pub fn`, a new module, a new exported component, a meaningful refactor).
+
+- **P0** — RCE, auth bypass, data loss, secrets in code, SQL injection, path traversal, broken auth on a public surface.
+- **P1** — significant bug, missing auth/authorization check on a new public surface, blocking I/O on a likely async path, race condition, missing input validation on caller-controlled parameters, observable performance regression.
+- **P2** — wrong module placement, doc/code mismatch, half-finished public abstractions (`pub fn` + `#[allow(dead_code)]` + `TODO`), `AGENTS.md` style violations, naming that contradicts the function's behavior.
+
+## Checklist for new public surfaces
+
+For any new `pub fn` / `pub async fn` / exported Svelte component / exported prop introduced by this PR, verify:
+
+- (a) auth/authorization expectations are documented in the doc comment OR enforced in the function body. A new `pub fn` that touches workspace data, secrets, files, or processes without an auth check or documented "caller MUST verify" contract is a P1.
+- (b) the function is placed in a module whose stated purpose matches what it does. Check the module-level doc comment (`//!`) — a config-file reader inside `external_ip.rs` is a P2.
+- (c) it is not half-finished. `pub fn` + `#[allow(dead_code)]` + a `TODO` is a smell that says the function should land together with its caller, not separately. Cite the relevant `AGENTS.md` rule.
+- (d) input validation defends against injection / traversal / overflow / NUL bytes at every parameter that may be caller-controlled.
+
+## Test coverage assessment
+
+End your review with a short "Test coverage" section calibrated to the layers actually changed by the diff. Skip categories the PR does not touch.
+
+- **Backend** (Rust under `backend/`) — expect Rust unit tests for new logic. For new or modified API handlers, worker steps, queue/cron behavior, or DB access, also expect or note the absence of integration tests. Pure-refactor backend PRs don't need new tests if existing tests cover the surface.
+- **Frontend** (Svelte / TS under `frontend/`) — the codebase does not generally test Svelte components, so do not ask for component tests. Only flag missing tests for new pure-logic utilities (the kind of file that already has a sibling `*.test.ts`, e.g. `flowDiff`, `previousResults`, copilot logic).
+- **CI / workflows / docs / config-only** — no automated tests expected; say so explicitly so the reader knows you considered it.
+
+Then state what manual verification, if any, is still needed before merge:
+
+- Describe each manual scenario as a short paragraph (not a numbered list): what page / action / input, and what observable outcome confirms correctness.
+- If the diff has no in-app surface to exercise (purely backend internals, CI, docs, or refactor), say that plainly.
+
+## Additional reviewer instructions
+
+If the prompt or context includes an "Additional reviewer instructions" section, treat it as extra guidance from the human who triggered this review and follow it.
+
+## Prior PR discussion
+
+If the prompt or context includes a "Prior PR discussion" section, this PR has already received review activity. Look for your own previous comment, take it into account, focus on what changed in the latest commits, and do not repeat findings the human already pushed back on or addressed.
diff --git a/ai_evals/AGENTS.md b/ai_evals/AGENTS.md
index 096baf5b58..d26e6d60ea 100644
--- a/ai_evals/AGENTS.md
+++ b/ai_evals/AGENTS.md
@@ -6,6 +6,7 @@ This folder contains black-box benchmark cases for:
- `app`
- `script`
- `cli`
+- `global`
The goal is to test the current production prompts and guidance with realistic user requests, not to test one exact implementation shape.
@@ -75,6 +76,16 @@ Still, avoid benchmark phrasing. The prompt should read like a repo task, not a
When relevant, ask the assistant to tell the user which `wmill` commands to run next. That is part of the benchmarked behavior.
+## Global-specific rules
+
+Global prompts should exercise workspace-level drafting behavior:
+
+- inspecting existing scripts, flows, apps, schedules, triggers, resources, and variables when relevant
+- writing AI drafts rather than saving or deploying by default
+- producing coherent multi-artifact changes when the request crosses artifact boundaries
+
+Keep deterministic validation focused on the draft contract: required draft type/path, required content snippets, forbidden draft paths, and forbidden mutating tools such as deploy/delete unless the case explicitly asks for them.
+
## Deterministic validation
Use deterministic validation only for hard failures such as:
diff --git a/ai_evals/README.md b/ai_evals/README.md
index 267451aabf..d5fe1661a2 100644
--- a/ai_evals/README.md
+++ b/ai_evals/README.md
@@ -1,11 +1,12 @@
# AI Evals
-Small benchmark runner for the four Windmill AI generation modes:
+Small benchmark runner for the Windmill AI generation modes:
- `cli`
- `flow`
- `script`
- `app`
+- `global`
The benchmark always tests the current production prompts, tools, and guidance in this checkout.
@@ -55,8 +56,9 @@ bun run cli -- run flow flow-test4-order-processing-loop --model opus
bun run cli -- run flow flow-test0-sum-two-numbers --models haiku,opus,4o
bun run cli -- run flow flow-test0-sum-two-numbers --runs 3 --verbose
bun run cli -- run flow --record
-GEMINI_API_KEY=... bun run cli -- run app app-test1-counter-create --model gemini-pro --transport proxy
+GEMINI_API_KEY=... bun run cli -- run app app-test1-counter-create --model gemini-3-flash-preview
WMILL_AI_EVAL_BACKEND_URL=http://127.0.0.1:8000 bun run cli -- run flow --backend-validation preview
+bun run cli -- run global global-test1-script-create
bun run cli -- run cli bun-hello-script
```
@@ -72,7 +74,6 @@ Public CLI surface:
- `--output `: custom result JSON path
- `--model `: choose the model under test
- `--models `: run the same cases sequentially against several model aliases
-- `--transport `: frontend request transport (`direct` by default, `proxy` to exercise `/api/w/{workspace}/ai/proxy`)
- `--verbose`: stream assistant output for frontend runs
- `--record`: append a compact tracked summary line to `ai_evals/history/.jsonl` for full-suite runs only
- `--backend-validation `: optional backend smoke validation (`off` or `preview`) for `script` and `flow` evals
@@ -87,15 +88,16 @@ Today:
- `sonnet`
- `opus`
- `4o`
-- `gemini-flash`
-- `gemini-pro`
+- `gpt-5.5`
- `gemini-3-flash-preview`
- `gemini-3.1-pro-preview`
+- `deepseek-v4-flash`
+- `deepseek-v4-pro`
Notes:
-- the command also prints accepted alias spellings such as `gpt-4o`, `claude-opus-4.6`, and `claude-haiku-4.5`
-- frontend modes (`flow`, `script`, `app`) can use Anthropic, OpenAI, and Gemini-backed aliases
+- the command also prints accepted alias spellings such as `gpt-4o`, `gpt-55`, `claude-opus-4.6`, and `claude-haiku-4.5`
+- frontend modes (`flow`, `script`, `app`, `global`) can use Anthropic, OpenAI, Gemini, and DeepSeek-backed aliases
- `cli` mode always uses the Anthropic agent SDK, so only Anthropic aliases are valid there
- the judge model is separate and currently defaults to `claude-sonnet-4-6`
@@ -134,6 +136,22 @@ For `app` mode, `validate` can express narrow hard requirements such as:
- minimum datatable / datatable-table counts
- specific required datatable tables
+For `global` mode, `validate` can express draft-level requirements such as:
+
+- required draft type/path/language
+- required or forbidden snippets in draft values
+- required or forbidden draft counts
+- forbidden draft paths
+
+Global initial fixtures can also seed `liveEditorDrafts` with `type`,
+`storagePath`, `effectivePath`, and `value` fields. These drafts emulate the
+currently open script, flow, or raw app editor so cases can test prompts that
+refer to "this" or the "current" item.
+
+Set `WMILL_AI_EVAL_DISABLE_ACTIVE_EDITOR_CONTEXT=1` to run those cases with
+the old behavior where the live editor is only discoverable through
+`list_workspace_items`.
+
App fixtures can also include an optional `datatables.json` file at the fixture root.
For `flow` mode, an `initial` fixture can also include a benchmark workspace catalog of
@@ -145,26 +163,23 @@ If `--backend-validation preview` is enabled:
- `script` evals run a real backend script preview in an isolated temp workspace
- `flow` evals run a real backend flow preview only for cases that define `runtime.backendPreview`
- `flow` cases with `initial.workspace` fixtures seed those scripts and flows into the preview workspace before preview
-- when `WMILL_AI_EVAL_BACKEND_WORKSPACE` is set, `ai_evals` treats that workspace as a dedicated test workspace, clears managed eval assets under `f/evals/*` before each preview run, and then reseeds the current case fixtures
+- when `WMILL_AI_EVAL_BACKEND_WORKSPACE` is set, `ai_evals` creates or reuses that workspace as a dedicated test workspace, clears managed eval assets under `f/evals/*` before each preview run, and then reseeds the current case fixtures
-Supported backend validation env vars:
+Supported backend env vars:
- `WMILL_AI_EVAL_BACKEND_VALIDATION=preview`
- `WMILL_AI_EVAL_BACKEND_URL=http://127.0.0.1:8000`
- `WMILL_AI_EVAL_BACKEND_EMAIL=admin@windmill.dev`
- `WMILL_AI_EVAL_BACKEND_PASSWORD=changeme`
- `WMILL_AI_EVAL_BACKEND_WORKSPACE=integration-tests` to reuse an existing workspace on CE installs with low workspace limits
-- `WMILL_AI_EVAL_KEEP_WORKSPACES=1`
-- `WMILL_AI_EVAL_WORKSPACE_PREFIX=ai-evals`
-Frontend proxy transport uses the same backend auth/workspace env vars.
+Frontend modes require a reachable Windmill backend and send model requests through the workspace AI proxy at `/api/w/{workspace}/ai/proxy`. At startup, `ai_evals` checks the resolved backend URL and fails early with setup guidance if the backend cannot be reached or login fails.
-When `--transport proxy` is set:
+For frontend modes:
-- `ai_evals` creates or reuses a backend workspace
+- `ai_evals` creates a temporary backend workspace, or creates/reuses `WMILL_AI_EVAL_BACKEND_WORKSPACE` when it is set
- it upserts a provider resource under `f/evals/ai/`
- frontend requests go through `/api/w/{workspace}/ai/proxy`
-- result JSON and history records include `transport` so direct vs proxy runs stay distinguishable
## Results And Artifacts
@@ -178,16 +193,21 @@ If `--record` is used, the CLI also appends one compact JSON line to:
- `ai_evals/history/flow.jsonl`
- `ai_evals/history/script.jsonl`
- `ai_evals/history/app.jsonl`
+- `ai_evals/history/global.jsonl`
- `ai_evals/history/cli.jsonl`
Each recorded line contains:
-- run metadata (`createdAt`, `gitSha`, `mode`, `runModel`, `transport`, `judgeModel`)
-- suite totals (`caseCount`, `attemptCount`, `passedAttempts`, `passRate`, `averageDurationMs`, `averageJudgeScore`)
-- average token usage (`averageTokenUsagePerAttempt`)
-- per-case metrics under `cases[]` (`averageDurationMs`, `averageJudgeScore`, `averageTokenUsagePerAttempt`, pass rate)
+- run metadata (`createdAt`, `gitSha`, `mode`, `runModel`, `judgeModel`)
+- suite totals (`caseCount`, `attemptCount`, `passedAttempts`, `passRate`, `averageDurationMs`, `averagePassedDurationMs`, `averageJudgeScore`)
+- average token usage (`averageTokenUsagePerAttempt`, `averageTokenUsagePerPassedAttempt`)
+- per-case metrics under `cases[]` (`averageDurationMs`, `averagePassedDurationMs`, `averageJudgeScore`, `averageTokenUsagePerAttempt`, `averageTokenUsagePerPassedAttempt`, pass rate)
- `failedCaseIds`
+The CLI headline duration and token averages use passed attempts only.
+All-attempt averages are still recorded to make failures auditable without
+letting failed attempts skew success cost comparisons.
+
Example:
- summary: `ai_evals/results/2026-04-09T09-40-33.051Z__flow.json`
@@ -198,6 +218,7 @@ Typical artifacts by mode:
- `flow`: `flow.json`
- `script`: `script.json` plus the generated script file
- `app`: `app.json` plus frontend/backend files
+- `global`: `global-drafts.json`
- `cli`: `assistant-output.txt`, `trace.json`, `wmill-invocations.jsonl`, plus generated workspace files
- backend-validated attempts also include `backend-preview.json`
@@ -213,6 +234,7 @@ Typical artifacts by mode:
## Notes
- Frontend modes reuse the production frontend chat code through the Vitest bridge.
+- Global mode evaluates the production global AI tools and validates the resulting AI draft store.
- CLI mode creates an isolated workspace, writes the current checkout guidance into it, and benchmarks the real skills / `AGENTS.md` flow.
- CLI mode now also records a structured trace of invoked skills, tool calls, proposed `wmill` commands, and any attempted `wmill` executions.
- Frontend progress streams live while the benchmark is running.
diff --git a/ai_evals/adapters/frontend/backendPreview.test.ts b/ai_evals/adapters/frontend/backendPreview.test.ts
index 2f12c9a896..d4de361333 100644
--- a/ai_evals/adapters/frontend/backendPreview.test.ts
+++ b/ai_evals/adapters/frontend/backendPreview.test.ts
@@ -210,8 +210,6 @@ function buildSettings(
baseUrl: 'http://backend.test/default',
email: 'admin@windmill.dev',
password: 'changeme',
- keepWorkspaces: true,
- workspacePrefix: 'ai-evals',
pollIntervalMs: 1,
maxWaitMs: 50,
...overrides
diff --git a/ai_evals/adapters/frontend/backendPreview.ts b/ai_evals/adapters/frontend/backendPreview.ts
index e1be934564..57e1cfdf2a 100644
--- a/ai_evals/adapters/frontend/backendPreview.ts
+++ b/ai_evals/adapters/frontend/backendPreview.ts
@@ -24,6 +24,7 @@ export interface CompletedPreviewJob {
const tokenCache = new Map>()
const sharedWorkspaceQueue = new Map>()
const managedSharedWorkspacePrefixes = ['f/evals/']
+const DEFAULT_WORKSPACE_PREFIX = 'ai-evals'
export class BackendPreviewClient {
constructor(private readonly settings: BackendValidationSettings) {}
@@ -35,7 +36,7 @@ export class BackendPreviewClient {
): Promise {
const workspaceId =
this.settings.workspaceOverride ??
- buildWorkspaceId(this.settings.workspacePrefix, caseId, attempt)
+ buildWorkspaceId(caseId, attempt)
const run = async () => {
await this.ensureWorkspace(workspaceId)
@@ -46,7 +47,7 @@ export class BackendPreviewClient {
try {
return await body(workspaceId)
} finally {
- if (!this.settings.keepWorkspaces && !this.settings.workspaceOverride) {
+ if (!this.settings.workspaceOverride) {
await this.deleteWorkspace(workspaceId).catch(() => undefined)
}
}
@@ -440,14 +441,14 @@ async function withSharedWorkspaceLock(workspaceId: string, body: () => Promi
}
}
-function buildWorkspaceId(prefix: string, caseId: string, attempt: number): string {
+function buildWorkspaceId(caseId: string, attempt: number): string {
const caseSlug = caseId
.toLowerCase()
.replace(/[^a-z0-9-]+/g, '-')
.replace(/^-+|-+$/g, '')
.slice(0, 30)
const suffix = randomUUID().slice(0, 8)
- return `${prefix}-${caseSlug || 'case'}-a${attempt}-${suffix}`
+ return `${DEFAULT_WORKSPACE_PREFIX}-${caseSlug || 'case'}-a${attempt}-${suffix}`
}
function extractFolderName(path: string): string | null {
diff --git a/ai_evals/adapters/frontend/benchmarkRunner.ts b/ai_evals/adapters/frontend/benchmarkRunner.ts
index 474d434803..1729df7170 100644
--- a/ai_evals/adapters/frontend/benchmarkRunner.ts
+++ b/ai_evals/adapters/frontend/benchmarkRunner.ts
@@ -1,6 +1,5 @@
import { loadSelectedCases } from "../../core/cases";
import { resolveBackendValidationSettings } from "../../core/backendValidation";
-import { resolveFrontendEvalTransportSettings } from "../../core/frontendTransport";
import {
formatRunModelLabel,
getFrontendEvalModel,
@@ -9,13 +8,11 @@ import {
import { buildRunResult } from "../../core/results";
import { runSuite } from "../../core/runSuite";
import type { BenchmarkRunResult, ModeRunner } from "../../core/types";
+import { resolveWindmillBackendSettings } from "../../core/windmillBackendSettings";
import { emitFrontendBenchmarkProgress } from "./progress";
-import { createAppModeRunner } from "../../modes/app";
-import { createFlowModeRunner } from "../../modes/flow";
-import { createScriptModeRunner } from "../../modes/script";
import { DEFAULT_JUDGE_MODEL } from "../../core/judge";
-export type FrontendBenchmarkMode = "flow" | "app" | "script";
+export type FrontendBenchmarkMode = "flow" | "app" | "script" | "global";
export async function runFrontendBenchmarkFromEnv(): Promise {
const mode = parseMode(process.env.WMILL_FRONTEND_AI_EVAL_MODE);
@@ -36,17 +33,14 @@ export async function runFrontendBenchmarkFromEnv(): Promise
evalMode: mode,
requestedMode: process.env.WMILL_FRONTEND_AI_EVAL_BACKEND_VALIDATION,
});
- const transportSettings = resolveFrontendEvalTransportSettings({
- evalMode: mode,
- requestedTransport: process.env.WMILL_FRONTEND_AI_EVAL_TRANSPORT,
- });
+ const backendSettings = resolveWindmillBackendSettings();
const selectedCases = await loadSelectedCases(mode, caseIds);
- const modeRunner = getModeRunner(
+ const modeRunner = await getModeRunner(
mode,
getFrontendEvalModel(model),
backendValidation,
- transportSettings,
+ backendSettings,
);
const runModel = formatRunModelLabel(mode, model);
const caseResults = await runSuite({
@@ -66,34 +60,43 @@ export async function runFrontendBenchmarkFromEnv(): Promise
mode,
runs,
runModel,
- transport: transportSettings.transport,
judgeModel: DEFAULT_JUDGE_MODEL,
caseResults,
});
}
-function getModeRunner(
+async function getModeRunner(
mode: FrontendBenchmarkMode,
model: ReturnType,
backendValidation: ReturnType,
- transportSettings: ReturnType,
-): ModeRunner {
+ backendSettings: ReturnType,
+): Promise> {
switch (mode) {
- case "flow":
- return createFlowModeRunner(model, backendValidation, transportSettings);
- case "app":
- return createAppModeRunner(model, transportSettings);
- case "script":
+ case "flow": {
+ const { createFlowModeRunner } = await import("../../modes/flow");
+ return createFlowModeRunner(model, backendValidation, backendSettings);
+ }
+ case "app": {
+ const { createAppModeRunner } = await import("../../modes/app");
+ return createAppModeRunner(model, backendSettings);
+ }
+ case "script": {
+ const { createScriptModeRunner } = await import("../../modes/script");
return createScriptModeRunner(
model,
backendValidation,
- transportSettings,
+ backendSettings,
);
+ }
+ case "global": {
+ const { createGlobalModeRunner } = await import("../../modes/global");
+ return createGlobalModeRunner(model, backendSettings);
+ }
}
}
function parseMode(value: string | undefined): FrontendBenchmarkMode {
- if (value === "flow" || value === "app" || value === "script") {
+ if (value === "flow" || value === "app" || value === "script" || value === "global") {
return value;
}
throw new Error(`Unsupported frontend benchmark mode: ${String(value)}`);
diff --git a/ai_evals/adapters/frontend/core/app/appEvalRunner.ts b/ai_evals/adapters/frontend/core/app/appEvalRunner.ts
index 55d1e6ab9a..16543b28de 100644
--- a/ai_evals/adapters/frontend/core/app/appEvalRunner.ts
+++ b/ai_evals/adapters/frontend/core/app/appEvalRunner.ts
@@ -12,7 +12,7 @@ import {
prepareAppUserMessage,
} from "../../../../../frontend/src/lib/components/copilot/chat/app/core";
import type { Tool as ProductionTool } from "../../../../../frontend/src/lib/components/copilot/chat/shared";
-import { createAppFileHelpers } from "./fileHelpers";
+import { createAppFileHelpers, type AppEvalChatHelpers } from "./fileHelpers";
import { runEval } from "../shared";
import type { AIProvider } from "$lib/gen/types.gen";
import type {
@@ -22,7 +22,6 @@ import type {
} from "../../../../core/types";
import type { TokenUsage } from "../shared/types";
import type { AppFilesState } from "../../../../core/validators";
-import type { FrontendEvalTransport } from "../../../../core/frontendTransport";
import type { WindmillBackendSettings } from "../../../../core/windmillBackendSettings";
import {
createAppBackendRunnableContextElement,
@@ -49,8 +48,7 @@ export interface AppEvalOptions {
model?: string;
maxIterations?: number;
provider?: AIProvider;
- transport?: FrontendEvalTransport;
- backend?: WindmillBackendSettings;
+ backend: WindmillBackendSettings;
workspaceRoot?: string;
runContext?: ModeRunContext;
}
@@ -58,7 +56,7 @@ export interface AppEvalOptions {
export async function runAppEval(
userPrompt: string,
apiKey: string,
- options?: AppEvalOptions,
+ options: AppEvalOptions,
): Promise {
const workspaceRoot =
options?.workspaceRoot ??
@@ -101,10 +99,9 @@ export async function runAppEval(
model,
workspace: workspaceRoot,
provider: options?.provider,
- transport: options?.transport,
- backend: options?.backend,
- proxyCaseId: options?.runContext?.caseId,
- proxyAttempt: options?.runContext?.attempt,
+ backend: options.backend,
+ caseId: options?.runContext?.caseId,
+ attempt: options?.runContext?.attempt,
},
});
@@ -124,7 +121,7 @@ export async function runAppEval(
async function buildAdditionalContext(
appContext: EvalCaseRuntimeAppContextSpec | undefined,
- helpers: AppAIChatHelpers,
+ helpers: AppEvalChatHelpers,
): Promise {
const entries = appContext?.additional ?? [];
if (entries.length === 0) {
diff --git a/ai_evals/adapters/frontend/core/app/fileHelpers.ts b/ai_evals/adapters/frontend/core/app/fileHelpers.ts
index e82ddf4672..15721a49ed 100644
--- a/ai_evals/adapters/frontend/core/app/fileHelpers.ts
+++ b/ai_evals/adapters/frontend/core/app/fileHelpers.ts
@@ -2,6 +2,7 @@ import { mkdir, rm, writeFile } from 'fs/promises'
import { dirname, join } from 'path'
import type {
AppAIChatHelpers,
+ AppDatatableMetadata,
AppFiles,
BackendRunnable,
DataTableSchema,
@@ -10,6 +11,10 @@ import type {
} from '../../../../../frontend/src/lib/components/copilot/chat/app/core'
import { buildAppWmillTypes, collectAppDiagnostics } from '../../../../core/appDiagnostics'
+export interface AppEvalChatHelpers extends AppAIChatHelpers {
+ getDatatables: () => Promise
+}
+
async function writeFrontendFile(
workspaceRoot: string | undefined,
path: string,
@@ -92,7 +97,7 @@ export async function createAppFileHelpers(
initialDatatables: DataTableSchema[] = [],
workspaceRoot?: string
): Promise<{
- helpers: AppAIChatHelpers
+ helpers: AppEvalChatHelpers
getFiles: () => AppFiles
getEvalState: () => {
frontend: Record
@@ -137,7 +142,7 @@ export async function createAppFileHelpers(
}
await persistDatatables(workspaceRoot, datatables)
- const helpers: AppAIChatHelpers = {
+ const helpers: AppEvalChatHelpers = {
listFrontendFiles: () => [
...Object.keys(frontend).filter((path) => path !== '/wmill.d.ts'),
'/wmill.d.ts'
@@ -211,6 +216,34 @@ export async function createAppFileHelpers(
},
lint,
getDatatables: async () => structuredClone(datatables),
+ listDatatableTables: async () =>
+ datatables.map(
+ (datatable): AppDatatableMetadata => {
+ const schemas = Object.fromEntries(
+ Object.entries(datatable.schemas).map(([schemaName, tables]) => [
+ schemaName,
+ Object.keys(tables)
+ ])
+ )
+ return {
+ datatable_name: datatable.datatable_name,
+ schemas,
+ tableCount: Object.values(schemas).reduce(
+ (sum, tableNames) => sum + tableNames.length,
+ 0
+ ),
+ error: datatable.error
+ }
+ }
+ ),
+ getDatatableTableSchema: async (
+ datatableName: string,
+ schemaName: string,
+ tableName: string
+ ) => {
+ const datatable = datatables.find((entry) => entry.datatable_name === datatableName)
+ return structuredClone(datatable?.schemas?.[schemaName]?.[tableName] ?? {})
+ },
getAvailableDatatableNames: () => datatables.map((datatable) => datatable.datatable_name),
execDatatableSql: async (
datatableName: string,
diff --git a/ai_evals/adapters/frontend/core/flow/flowEvalRunner.ts b/ai_evals/adapters/frontend/core/flow/flowEvalRunner.ts
index 1b448bdea4..0cd25f5787 100644
--- a/ai_evals/adapters/frontend/core/flow/flowEvalRunner.ts
+++ b/ai_evals/adapters/frontend/core/flow/flowEvalRunner.ts
@@ -18,7 +18,6 @@ import {
import { runEval } from "../shared";
import type { ModeRunContext } from "../../../../core/types";
import type { TokenUsage, ToolCallDetail } from "../shared/types";
-import type { FrontendEvalTransport } from "../../../../core/frontendTransport";
import type { WindmillBackendSettings } from "../../../../core/windmillBackendSettings";
export interface FlowFixture {
@@ -48,8 +47,7 @@ export interface FlowEvalOptions {
model?: string;
maxIterations?: number;
provider?: AIProvider;
- transport?: FrontendEvalTransport;
- backend?: WindmillBackendSettings;
+ backend: WindmillBackendSettings;
workspaceRoot?: string;
runContext?: ModeRunContext;
}
@@ -57,7 +55,7 @@ export interface FlowEvalOptions {
export async function runFlowEval(
userPrompt: string,
apiKey: string,
- options?: FlowEvalOptions,
+ options: FlowEvalOptions,
): Promise {
const workspaceRoot =
options?.workspaceRoot ??
@@ -100,10 +98,9 @@ export async function runFlowEval(
model,
workspace: workspaceRoot,
provider: options?.provider,
- transport: options?.transport,
- backend: options?.backend,
- proxyCaseId: options?.runContext?.caseId,
- proxyAttempt: options?.runContext?.attempt,
+ backend: options.backend,
+ caseId: options?.runContext?.caseId,
+ attempt: options?.runContext?.attempt,
},
});
diff --git a/ai_evals/adapters/frontend/core/global/globalEvalRunner.ts b/ai_evals/adapters/frontend/core/global/globalEvalRunner.ts
new file mode 100644
index 0000000000..058adc3644
--- /dev/null
+++ b/ai_evals/adapters/frontend/core/global/globalEvalRunner.ts
@@ -0,0 +1,185 @@
+import { mkdtemp, rm } from "fs/promises";
+import { tmpdir } from "os";
+import { join } from "path";
+import type { AIProvider } from "$lib/gen/types.gen";
+import {
+ globalTools,
+ prepareGlobalSystemMessage,
+ prepareGlobalUserMessage,
+} from "../../../../../frontend/src/lib/components/copilot/chat/global/core";
+import {
+ clearGlobalDrafts,
+ listGlobalDrafts,
+} from "../../../../../frontend/src/lib/components/copilot/chat/global/userDraftAdapter";
+import type { Tool as ProductionTool } from "../../../../../frontend/src/lib/components/copilot/chat/shared";
+import { UserDraft } from "../../../../../frontend/src/lib/userDraft.svelte";
+import type { ModeRunContext } from "../../../../core/types";
+import type { GlobalDraftState } from "../../../../core/validators";
+import type { WindmillBackendSettings } from "../../../../core/windmillBackendSettings";
+import {
+ registerBenchmarkWorkspaceRunnables,
+ unregisterBenchmarkWorkspaceRunnables,
+ type BenchmarkWorkspaceRunnables,
+} from "../../mockBackend";
+import { runEval } from "../shared";
+import type { TokenUsage, ToolCallDetail } from "../shared/types";
+
+const MUTATING_GLOBAL_TOOLS = new Set([
+ "deploy_workspace_item",
+ "delete_workspace_item",
+]);
+const DISABLE_ACTIVE_EDITOR_CONTEXT_ENV =
+ "WMILL_AI_EVAL_DISABLE_ACTIVE_EDITOR_CONTEXT";
+
+const LIVE_EDITOR_ITEM_KINDS = {
+ script: "script",
+ flow: "flow",
+ app: "raw_app",
+} as const;
+
+export interface GlobalLiveEditorDraftFixture {
+ type: keyof typeof LIVE_EDITOR_ITEM_KINDS;
+ storagePath?: string;
+ effectivePath?: string;
+ value?: unknown;
+}
+
+export interface GlobalEvalResult {
+ success: boolean;
+ state: GlobalDraftState;
+ error?: string;
+ assistantMessageCount: number;
+ toolCallCount: number;
+ toolsUsed: string[];
+ toolCallDetails: ToolCallDetail[];
+ tokenUsage: TokenUsage;
+}
+
+export interface GlobalEvalOptions {
+ workspaceFixtures?: BenchmarkWorkspaceRunnables;
+ liveEditorDrafts?: GlobalLiveEditorDraftFixture[];
+ model?: string;
+ maxIterations?: number;
+ provider?: AIProvider;
+ backend: WindmillBackendSettings;
+ workspaceRoot?: string;
+ runContext?: ModeRunContext;
+}
+
+export async function runGlobalEval(
+ userPrompt: string,
+ apiKey: string,
+ options: GlobalEvalOptions,
+): Promise {
+ const workspaceRoot =
+ options.workspaceRoot ??
+ (await mkdtemp(join(tmpdir(), "wmill-frontend-global-benchmark-")));
+
+ clearGlobalDrafts(workspaceRoot);
+ registerBenchmarkWorkspaceRunnables(workspaceRoot, options.workspaceFixtures ?? {});
+ seedLiveEditorDrafts(workspaceRoot, options.liveEditorDrafts ?? []);
+
+ try {
+ const model = options.model ?? "claude-haiku-4-5-20251001";
+ const injectActiveEditorContext =
+ process.env[DISABLE_ACTIVE_EDITOR_CONTEXT_ENV] !== "1";
+ const rawResult = await runEval({
+ userPrompt,
+ systemMessage: prepareGlobalSystemMessage(),
+ userMessage: prepareGlobalUserMessage(
+ userPrompt,
+ [],
+ injectActiveEditorContext ? { workspace: workspaceRoot } : {},
+ ),
+ tools: getGlobalEvalTools(),
+ helpers: {},
+ apiKey,
+ getOutput: () => ({ drafts: listGlobalDrafts(workspaceRoot) }),
+ onAssistantMessageStart: options.runContext?.onAssistantMessageStart,
+ onAssistantToken: options.runContext?.onAssistantChunk,
+ onAssistantMessageEnd: options.runContext?.onAssistantMessageEnd,
+ onToolCall: options.runContext?.onToolCall,
+ options: {
+ maxIterations: options.maxIterations,
+ model,
+ workspace: workspaceRoot,
+ provider: options.provider,
+ backend: options.backend,
+ caseId: options.runContext?.caseId,
+ attempt: options.runContext?.attempt,
+ },
+ });
+
+ return {
+ state: rawResult.output,
+ success: rawResult.success,
+ error: rawResult.error,
+ assistantMessageCount: rawResult.iterations,
+ toolCallCount: rawResult.toolCallsCount,
+ toolsUsed: rawResult.toolsCalled,
+ toolCallDetails: rawResult.toolCallDetails,
+ tokenUsage: rawResult.tokenUsage,
+ };
+ } finally {
+ clearGlobalDrafts(workspaceRoot);
+ clearLiveEditorDrafts(workspaceRoot, options.liveEditorDrafts ?? []);
+ unregisterBenchmarkWorkspaceRunnables(workspaceRoot);
+ if (!options.workspaceRoot) {
+ await rm(workspaceRoot, { recursive: true, force: true });
+ }
+ }
+}
+
+function seedLiveEditorDrafts(
+ workspace: string,
+ fixtures: GlobalLiveEditorDraftFixture[],
+): void {
+ for (const fixture of fixtures) {
+ const itemKind = LIVE_EDITOR_ITEM_KINDS[fixture.type];
+ const storagePath = fixture.storagePath ?? fixture.effectivePath ?? "";
+ if (fixture.value !== undefined) {
+ UserDraft.save(itemKind, storagePath, fixture.value, { workspace });
+ }
+ UserDraft.setLiveEditorDraft({
+ workspace,
+ itemKind,
+ storagePath,
+ effectivePath: fixture.effectivePath ?? fixture.storagePath,
+ });
+ }
+}
+
+function clearLiveEditorDrafts(
+ workspace: string,
+ fixtures: GlobalLiveEditorDraftFixture[],
+): void {
+ for (const fixture of fixtures) {
+ const itemKind = LIVE_EDITOR_ITEM_KINDS[fixture.type];
+ const storagePath = fixture.storagePath ?? fixture.effectivePath ?? "";
+ UserDraft.clearLiveEditorDraft(itemKind, { workspace, storagePath });
+ }
+}
+
+function getGlobalEvalTools(): ProductionTool<{}>[] {
+ return (globalTools as ProductionTool<{}>[]).map((tool) => {
+ if (!MUTATING_GLOBAL_TOOLS.has(tool.def.function.name)) {
+ return tool;
+ }
+
+ return {
+ ...tool,
+ requiresConfirmation: false,
+ validateBeforeConfirmation: undefined,
+ fn: async () =>
+ JSON.stringify(
+ {
+ success: false,
+ error:
+ "This mutating workspace tool is disabled during ai_evals global mode.",
+ },
+ null,
+ 2,
+ ),
+ };
+ });
+}
diff --git a/ai_evals/adapters/frontend/core/script/scriptEvalRunner.ts b/ai_evals/adapters/frontend/core/script/scriptEvalRunner.ts
index 30aa2c81e7..95ce6555e6 100644
--- a/ai_evals/adapters/frontend/core/script/scriptEvalRunner.ts
+++ b/ai_evals/adapters/frontend/core/script/scriptEvalRunner.ts
@@ -14,7 +14,6 @@ import { createScriptFileHelpers, type ScriptEvalState } from "./fileHelpers";
import { runEval } from "../shared";
import type { ModeRunContext } from "../../../../core/types";
import type { TokenUsage, ToolCallDetail } from "../shared/types";
-import type { FrontendEvalTransport } from "../../../../core/frontendTransport";
import type { WindmillBackendSettings } from "../../../../core/windmillBackendSettings";
export interface ScriptEvalResult {
@@ -33,8 +32,7 @@ export interface ScriptEvalOptions {
model?: string;
maxIterations?: number;
provider?: AIProvider;
- transport?: FrontendEvalTransport;
- backend?: WindmillBackendSettings;
+ backend: WindmillBackendSettings;
workspaceRoot?: string;
runContext?: ModeRunContext;
}
@@ -98,10 +96,9 @@ export async function runScriptEval(
model,
workspace: workspaceRoot,
provider: modelProvider.provider,
- transport: options.transport,
backend: options.backend,
- proxyCaseId: options.runContext?.caseId,
- proxyAttempt: options.runContext?.attempt,
+ caseId: options.runContext?.caseId,
+ attempt: options.runContext?.attempt,
},
});
diff --git a/ai_evals/adapters/frontend/core/shared/baseEvalRunner.ts b/ai_evals/adapters/frontend/core/shared/baseEvalRunner.ts
index 5b1f2e948e..5f4ea2c307 100644
--- a/ai_evals/adapters/frontend/core/shared/baseEvalRunner.ts
+++ b/ai_evals/adapters/frontend/core/shared/baseEvalRunner.ts
@@ -40,8 +40,8 @@ export interface RunEvalParams {
apiKey: string;
/** Function to get the current output state */
getOutput: () => TOutput;
- /** Optional configuration */
- options?: EvalRunnerOptions;
+ /** Model and Windmill backend configuration */
+ options: EvalRunnerOptions;
onAssistantMessageStart?: () => void;
onAssistantToken?: (token: string) => void;
onAssistantMessageEnd?: () => void;
@@ -70,10 +70,10 @@ export async function runEval(
} = params;
let shouldEmitMessageStart = true;
- const model = options?.model ?? "gpt-4o";
- const maxIterations = options?.maxIterations ?? 20;
- const workspace = options?.workspace ?? "test-workspace";
- const provider = toFrontendEvalProvider(options?.provider);
+ const model = options.model ?? "gpt-4o";
+ const maxIterations = options.maxIterations ?? 20;
+ const workspace = options.workspace ?? "test-workspace";
+ const provider = toFrontendEvalProvider(options.provider);
const modelProvider = resolveEvalModelProvider(model, provider);
@@ -203,45 +203,31 @@ export async function runEval(
}
};
- if (options?.transport === "proxy") {
- const backendSettings = options.backend;
- if (!backendSettings) {
- throw new Error("Missing backend settings for proxy transport");
- }
-
- const backendClient = new WindmillBackendClient(backendSettings);
- return await backendClient.withWorkspace(
- options.proxyCaseId ?? "eval",
- options.proxyAttempt ?? 1,
- async (proxyWorkspaceId) => {
- const resourcePath = buildProxyResourcePath(modelProvider.provider);
- await backendClient.upsertResource({
- workspaceId: proxyWorkspaceId,
- path: resourcePath,
- resourceType: modelProvider.provider,
- value: { api_key: apiKey },
- });
- const token = await backendClient.getToken();
- const clients = createEvalClients({
- provider: modelProvider.provider,
- apiKey,
- transport: "proxy",
- proxy: {
- baseURL: `${backendSettings.baseUrl}/api/w/${encodeURIComponent(proxyWorkspaceId)}/ai/proxy`,
- bearerToken: token,
- resourcePath,
- },
- }) as unknown as ChatClients;
- return await executeChatLoop(clients);
- },
- );
- }
-
- const clients = createEvalClients({
- provider: modelProvider.provider,
- apiKey,
- }) as unknown as ChatClients;
- return await executeChatLoop(clients);
+ const backendSettings = options.backend;
+ const backendClient = new WindmillBackendClient(backendSettings);
+ return await backendClient.withWorkspace(
+ options.caseId ?? "eval",
+ options.attempt ?? 1,
+ async (proxyWorkspaceId) => {
+ const resourcePath = buildProxyResourcePath(modelProvider.provider);
+ await backendClient.upsertResource({
+ workspaceId: proxyWorkspaceId,
+ path: resourcePath,
+ resourceType: modelProvider.provider,
+ value: { api_key: apiKey },
+ });
+ const token = await backendClient.getToken();
+ const clients = createEvalClients({
+ provider: modelProvider.provider,
+ proxy: {
+ baseURL: `${backendSettings.baseUrl}/api/w/${encodeURIComponent(proxyWorkspaceId)}/ai/proxy`,
+ bearerToken: token,
+ resourcePath,
+ },
+ }) as unknown as ChatClients;
+ return await executeChatLoop(clients);
+ },
+ );
}
function toFrontendEvalProvider(
@@ -250,7 +236,8 @@ function toFrontendEvalProvider(
if (
provider === "anthropic" ||
provider === "openai" ||
- provider === "googleai"
+ provider === "googleai" ||
+ provider === "deepseek"
) {
return provider;
}
diff --git a/ai_evals/adapters/frontend/core/shared/providerConfig.test.ts b/ai_evals/adapters/frontend/core/shared/providerConfig.test.ts
index e504ced376..01a55e048e 100644
--- a/ai_evals/adapters/frontend/core/shared/providerConfig.test.ts
+++ b/ai_evals/adapters/frontend/core/shared/providerConfig.test.ts
@@ -2,35 +2,9 @@ import { describe, expect, it } from "bun:test";
import {
buildProxyHeaders,
buildProxyResourcePath,
- buildOpenAICompatibleClientOptions,
resolveEvalModelProvider,
} from "./providerConfig";
-describe("buildOpenAICompatibleClientOptions", () => {
- it("adds Gemini's OpenAI-compatible base URL and client header", () => {
- const options = buildOpenAICompatibleClientOptions(
- "googleai",
- "gemini-test-key",
- );
-
- expect(options).toMatchObject({
- apiKey: "gemini-test-key",
- baseURL: "https://generativelanguage.googleapis.com/v1beta/openai/",
- defaultHeaders: {
- "x-goog-api-client": "windmill-ai-evals/1.0",
- },
- });
- });
-
- it("keeps the default OpenAI-compatible config for OpenAI", () => {
- expect(
- buildOpenAICompatibleClientOptions("openai", "openai-test-key"),
- ).toEqual({
- apiKey: "openai-test-key",
- });
- });
-});
-
describe("proxy helpers", () => {
it("builds provider-scoped proxy resource paths", () => {
expect(buildProxyResourcePath("googleai")).toBe("f/evals/ai/googleai");
@@ -47,16 +21,25 @@ describe("proxy helpers", () => {
describe("resolveEvalModelProvider", () => {
it("infers googleai from Gemini model ids", () => {
- expect(resolveEvalModelProvider("gemini-2.5-flash")).toEqual({
+ expect(resolveEvalModelProvider("gemini-3-flash-preview")).toEqual({
provider: "googleai",
- model: "gemini-2.5-flash",
+ model: "gemini-3-flash-preview",
+ });
+ });
+
+ it("infers deepseek from DeepSeek model ids", () => {
+ expect(resolveEvalModelProvider("deepseek-v4-flash")).toEqual({
+ provider: "deepseek",
+ model: "deepseek-v4-flash",
});
});
it("preserves an explicit provider", () => {
- expect(resolveEvalModelProvider("gemini-2.5-pro", "googleai")).toEqual({
+ expect(
+ resolveEvalModelProvider("gemini-3.1-pro-preview", "googleai"),
+ ).toEqual({
provider: "googleai",
- model: "gemini-2.5-pro",
+ model: "gemini-3.1-pro-preview",
});
});
});
diff --git a/ai_evals/adapters/frontend/core/shared/providerConfig.ts b/ai_evals/adapters/frontend/core/shared/providerConfig.ts
index 62ba221a40..9d9e315217 100644
--- a/ai_evals/adapters/frontend/core/shared/providerConfig.ts
+++ b/ai_evals/adapters/frontend/core/shared/providerConfig.ts
@@ -1,7 +1,6 @@
import Anthropic from "@anthropic-ai/sdk";
import OpenAI from "openai";
import type { FrontendEvalModelConfig } from "../../../../core/models";
-import type { FrontendEvalTransport } from "../../../../core/frontendTransport";
export type FrontendEvalProvider = FrontendEvalModelConfig["provider"];
@@ -15,15 +14,12 @@ export interface ResolvedEvalModelProvider {
model: string;
}
-export interface EvalProxyClientConfig {
+export interface WindmillAiProxyClientConfig {
baseURL: string;
bearerToken: string;
resourcePath: string;
}
-const GEMINI_OPENAI_BASE_URL =
- "https://generativelanguage.googleapis.com/v1beta/openai/";
-const GEMINI_GOOG_API_CLIENT = "windmill-ai-evals/1.0";
const EVAL_PROXY_RESOURCE_PREFIX = "f/evals/ai";
export function buildProxyHeaders(
@@ -40,25 +36,8 @@ export function buildProxyResourcePath(provider: FrontendEvalProvider): string {
return `${EVAL_PROXY_RESOURCE_PREFIX}/${provider}`;
}
-export function buildOpenAICompatibleClientOptions(
- provider: Exclude,
- apiKey: string,
-): ConstructorParameters[0] {
- if (provider === "googleai") {
- return {
- apiKey,
- baseURL: GEMINI_OPENAI_BASE_URL,
- defaultHeaders: {
- "x-goog-api-client": GEMINI_GOOG_API_CLIENT,
- },
- };
- }
-
- return { apiKey };
-}
-
function buildProxyOpenAIClientOptions(
- proxy: EvalProxyClientConfig,
+ proxy: WindmillAiProxyClientConfig,
): ConstructorParameters[0] {
return {
apiKey: "unused",
@@ -69,52 +48,24 @@ function buildProxyOpenAIClientOptions(
export function createEvalClients(input: {
provider: FrontendEvalProvider;
- apiKey: string;
- transport?: FrontendEvalTransport;
- proxy?: EvalProxyClientConfig;
+ proxy: WindmillAiProxyClientConfig;
}): EvalClients {
- const transport = input.transport ?? "direct";
-
if (input.provider === "anthropic") {
- if (transport === "proxy") {
- if (!input.proxy) {
- throw new Error(
- "Missing proxy client configuration for proxy transport",
- );
- }
- return {
- openai: new OpenAI({ apiKey: "unused" }),
- anthropic: new Anthropic({
- apiKey: "unused",
- baseURL: input.proxy.baseURL,
- defaultHeaders: buildProxyHeaders(
- input.proxy.bearerToken,
- input.proxy.resourcePath,
- ),
- }),
- };
- }
-
return {
openai: new OpenAI({ apiKey: "unused" }),
- anthropic: new Anthropic({ apiKey: input.apiKey }),
- };
- }
-
- if (transport === "proxy") {
- if (!input.proxy) {
- throw new Error("Missing proxy client configuration for proxy transport");
- }
- return {
- openai: new OpenAI(buildProxyOpenAIClientOptions(input.proxy)),
- anthropic: new Anthropic({ apiKey: "unused" }),
+ anthropic: new Anthropic({
+ apiKey: "unused",
+ baseURL: input.proxy.baseURL,
+ defaultHeaders: buildProxyHeaders(
+ input.proxy.bearerToken,
+ input.proxy.resourcePath,
+ ),
+ }),
};
}
return {
- openai: new OpenAI(
- buildOpenAICompatibleClientOptions(input.provider, input.apiKey),
- ),
+ openai: new OpenAI(buildProxyOpenAIClientOptions(input.proxy)),
anthropic: new Anthropic({ apiKey: "unused" }),
};
}
@@ -132,6 +83,9 @@ export function resolveEvalModelProvider(
if (model.startsWith("gemini")) {
return { provider: "googleai", model };
}
+ if (model.startsWith("deepseek")) {
+ return { provider: "deepseek", model };
+ }
if (model.startsWith("gpt") || model.startsWith("o")) {
return { provider: "openai", model };
}
diff --git a/ai_evals/adapters/frontend/core/shared/types.ts b/ai_evals/adapters/frontend/core/shared/types.ts
index f2a3f04794..f081fe398a 100644
--- a/ai_evals/adapters/frontend/core/shared/types.ts
+++ b/ai_evals/adapters/frontend/core/shared/types.ts
@@ -1,6 +1,5 @@
import type { ChatCompletionMessageParam } from "openai/resources/chat/completions.mjs";
import type { AIProvider } from "$lib/gen/types.gen";
-import type { FrontendEvalTransport } from "../../../../core/frontendTransport";
import type { WindmillBackendSettings } from "../../../../core/windmillBackendSettings";
export interface TokenUsage {
@@ -15,14 +14,13 @@ export interface ToolCallDetail {
}
export interface EvalRunnerOptions {
+ backend: WindmillBackendSettings;
maxIterations?: number;
model?: string;
workspace?: string;
provider?: AIProvider;
- transport?: FrontendEvalTransport;
- backend?: WindmillBackendSettings;
- proxyCaseId?: string;
- proxyAttempt?: number;
+ caseId?: string;
+ attempt?: number;
}
export interface RawEvalResult {
diff --git a/ai_evals/adapters/frontend/progress.ts b/ai_evals/adapters/frontend/progress.ts
index b5b8f12c83..3a4810c4a3 100644
--- a/ai_evals/adapters/frontend/progress.ts
+++ b/ai_evals/adapters/frontend/progress.ts
@@ -1,4 +1,4 @@
-export type FrontendBenchmarkProgressSurface = 'flow' | 'app' | 'script'
+export type FrontendBenchmarkProgressSurface = 'flow' | 'app' | 'script' | 'global'
export type FrontendBenchmarkProgressEvent =
| {
diff --git a/ai_evals/adapters/frontend/runtime.ts b/ai_evals/adapters/frontend/runtime.ts
index 6a76fceeb0..347e15191c 100644
--- a/ai_evals/adapters/frontend/runtime.ts
+++ b/ai_evals/adapters/frontend/runtime.ts
@@ -16,14 +16,13 @@ const FRONTEND_BENCHMARK_TEST =
const FRONTEND_BENCHMARK_CONFIG =
"../ai_evals/adapters/frontend/vitest.config.ts";
-export type FrontendMode = "flow" | "app" | "script";
+export type FrontendMode = "flow" | "app" | "script" | "global";
export async function runFrontendBenchmarkAdapter(input: {
mode: FrontendMode;
caseIds: string[];
runs: number;
model?: string;
- transport?: string;
verbose?: boolean;
backendValidation?: string;
}): Promise {
@@ -44,10 +43,6 @@ export async function runFrontendBenchmarkAdapter(input: {
WMILL_FRONTEND_AI_EVAL_BACKEND_VALIDATION: input.backendValidation ?? "",
};
- if (input.transport) {
- env.WMILL_FRONTEND_AI_EVAL_TRANSPORT = input.transport;
- }
-
try {
await runVitestBenchmark(
path.join(FRONTEND_DIR, "node_modules", ".bin", "vitest"),
diff --git a/ai_evals/adapters/frontend/vitestAdapter.test.ts b/ai_evals/adapters/frontend/vitestAdapter.test.ts
index 542feaf89b..92c43aab06 100644
--- a/ai_evals/adapters/frontend/vitestAdapter.test.ts
+++ b/ai_evals/adapters/frontend/vitestAdapter.test.ts
@@ -65,6 +65,10 @@ vi.mock('$lib/gen', async () => {
hasBenchmarkWorkspace(data.workspace)
? (listBenchmarkScripts(data.workspace) ?? [])
: actual.ScriptService.listScripts(data),
+ existsScriptByPath: async (data: { workspace: string; path: string }) =>
+ hasBenchmarkWorkspace(data.workspace)
+ ? Boolean(getBenchmarkScriptByPath(data.workspace, data.path))
+ : actual.ScriptService.existsScriptByPath(data),
getScriptByPath: async (data: { workspace: string; path: string }) => {
if (hasBenchmarkWorkspace(data.workspace)) {
const script = getBenchmarkScriptByPath(data.workspace, data.path)
@@ -75,6 +79,16 @@ vi.mock('$lib/gen', async () => {
}
return actual.ScriptService.getScriptByPath(data)
},
+ getScriptByPathWithDraft: async (data: { workspace: string; path: string }) => {
+ if (hasBenchmarkWorkspace(data.workspace)) {
+ const script = getBenchmarkScriptByPath(data.workspace, data.path)
+ if (!script) {
+ throw new Error(`Script "${data.path}" not found in benchmark workspace`)
+ }
+ return script
+ }
+ return actual.ScriptService.getScriptByPathWithDraft(data)
+ },
getScriptByHash: async (data: { workspace: string; hash: string }) => {
if (hasBenchmarkWorkspace(data.workspace)) {
const script = getBenchmarkScriptByHash(data.workspace, data.hash)
@@ -91,6 +105,10 @@ vi.mock('$lib/gen', async () => {
hasBenchmarkWorkspace(data.workspace)
? (listBenchmarkFlows(data.workspace) ?? [])
: actual.FlowService.listFlows(data),
+ existsFlowByPath: async (data: { workspace: string; path: string }) =>
+ hasBenchmarkWorkspace(data.workspace)
+ ? Boolean(getBenchmarkFlowByPath(data.workspace, data.path))
+ : actual.FlowService.existsFlowByPath(data),
getFlowByPath: async (data: { workspace: string; path: string }) => {
if (hasBenchmarkWorkspace(data.workspace)) {
const flow = getBenchmarkFlowByPath(data.workspace, data.path)
@@ -100,6 +118,26 @@ vi.mock('$lib/gen', async () => {
return flow
}
return actual.FlowService.getFlowByPath(data)
+ },
+ getFlowByPathWithDraft: async (data: { workspace: string; path: string }) => {
+ if (hasBenchmarkWorkspace(data.workspace)) {
+ const flow = getBenchmarkFlowByPath(data.workspace, data.path)
+ if (!flow) {
+ throw new Error(`Flow "${data.path}" not found in benchmark workspace`)
+ }
+ return flow
+ }
+ return actual.FlowService.getFlowByPathWithDraft(data)
+ },
+ getFlowLatestVersion: async (data: { workspace: string; path: string }) => {
+ if (hasBenchmarkWorkspace(data.workspace)) {
+ const flow = getBenchmarkFlowByPath(data.workspace, data.path)
+ if (!flow) {
+ throw new Error(`Flow "${data.path}" not found in benchmark workspace`)
+ }
+ return { id: 1 }
+ }
+ return actual.FlowService.getFlowLatestVersion(data)
}
}),
JobService: wrapService(actual.JobService, {
@@ -142,6 +180,16 @@ vi.mock('$lib/gen', async () => {
}
}),
ScheduleService: wrapService(actual.ScheduleService, {
+ existsSchedule: async (data: { workspace: string; path: string }) =>
+ hasBenchmarkWorkspace(data.workspace) ? false : actual.ScheduleService.existsSchedule(data),
+ listSchedules: async (data: { workspace: string }) =>
+ hasBenchmarkWorkspace(data.workspace) ? [] : actual.ScheduleService.listSchedules(data),
+ getSchedule: async (data: { workspace: string; path: string }) => {
+ if (hasBenchmarkWorkspace(data.workspace)) {
+ throw new Error(`Schedule "${data.path}" not found in benchmark workspace`)
+ }
+ return actual.ScheduleService.getSchedule(data)
+ },
previewSchedule: async (data: { requestBody?: Record }) =>
previewBenchmarkSchedule(data),
createSchedule: async (data: { workspace: string; requestBody: Record }) =>
@@ -149,11 +197,167 @@ vi.mock('$lib/gen', async () => {
? createBenchmarkSchedule(data)
: actual.ScheduleService.createSchedule(data)
}),
+ ResourceService: wrapService(actual.ResourceService, {
+ existsResource: async (data: { workspace: string; path: string }) =>
+ hasBenchmarkWorkspace(data.workspace) ? false : actual.ResourceService.existsResource(data),
+ listResource: async (data: { workspace: string }) =>
+ hasBenchmarkWorkspace(data.workspace) ? [] : actual.ResourceService.listResource(data),
+ getResource: async (data: { workspace: string; path: string }) => {
+ if (hasBenchmarkWorkspace(data.workspace)) {
+ throw new Error(`Resource "${data.path}" not found in benchmark workspace`)
+ }
+ return actual.ResourceService.getResource(data)
+ },
+ queryResourceTypes: async (data: { workspace: string }) =>
+ hasBenchmarkWorkspace(data.workspace) ? [] : actual.ResourceService.queryResourceTypes(data)
+ }),
+ VariableService: wrapService(actual.VariableService, {
+ existsVariable: async (data: { workspace: string; path: string }) =>
+ hasBenchmarkWorkspace(data.workspace) ? false : actual.VariableService.existsVariable(data),
+ listVariable: async (data: { workspace: string }) =>
+ hasBenchmarkWorkspace(data.workspace) ? [] : actual.VariableService.listVariable(data),
+ getVariable: async (data: { workspace: string; path: string }) => {
+ if (hasBenchmarkWorkspace(data.workspace)) {
+ throw new Error(`Variable "${data.path}" not found in benchmark workspace`)
+ }
+ return actual.VariableService.getVariable(data)
+ }
+ }),
+ AppService: wrapService(actual.AppService, {
+ existsApp: async (data: { workspace: string; path: string }) =>
+ hasBenchmarkWorkspace(data.workspace) ? false : actual.AppService.existsApp(data),
+ listApps: async (data: { workspace: string }) =>
+ hasBenchmarkWorkspace(data.workspace) ? [] : actual.AppService.listApps(data),
+ getAppByPath: async (data: { workspace: string; path: string }) => {
+ if (hasBenchmarkWorkspace(data.workspace)) {
+ throw new Error(`App "${data.path}" not found in benchmark workspace`)
+ }
+ return actual.AppService.getAppByPath(data)
+ }
+ }),
HttpTriggerService: wrapService(actual.HttpTriggerService, {
+ existsHttpTrigger: async (data: { workspace: string; path: string }) =>
+ hasBenchmarkWorkspace(data.workspace) ? false : actual.HttpTriggerService.existsHttpTrigger(data),
+ listHttpTriggers: async (data: { workspace: string }) =>
+ hasBenchmarkWorkspace(data.workspace) ? [] : actual.HttpTriggerService.listHttpTriggers(data),
+ getHttpTrigger: async (data: { workspace: string; path: string }) => {
+ if (hasBenchmarkWorkspace(data.workspace)) {
+ throw new Error(`HTTP trigger "${data.path}" not found in benchmark workspace`)
+ }
+ return actual.HttpTriggerService.getHttpTrigger(data)
+ },
createHttpTrigger: async (data: { workspace: string; requestBody: Record }) =>
hasBenchmarkWorkspace(data.workspace)
? createBenchmarkHttpTrigger(data)
: actual.HttpTriggerService.createHttpTrigger(data)
+ }),
+ WebsocketTriggerService: wrapService(actual.WebsocketTriggerService, {
+ existsWebsocketTrigger: async (data: { workspace: string; path: string }) =>
+ hasBenchmarkWorkspace(data.workspace)
+ ? false
+ : actual.WebsocketTriggerService.existsWebsocketTrigger(data),
+ listWebsocketTriggers: async (data: { workspace: string }) =>
+ hasBenchmarkWorkspace(data.workspace)
+ ? []
+ : actual.WebsocketTriggerService.listWebsocketTriggers(data),
+ getWebsocketTrigger: async (data: { workspace: string; path: string }) => {
+ if (hasBenchmarkWorkspace(data.workspace)) {
+ throw new Error(`Websocket trigger "${data.path}" not found in benchmark workspace`)
+ }
+ return actual.WebsocketTriggerService.getWebsocketTrigger(data)
+ }
+ }),
+ KafkaTriggerService: wrapService(actual.KafkaTriggerService, {
+ existsKafkaTrigger: async (data: { workspace: string; path: string }) =>
+ hasBenchmarkWorkspace(data.workspace)
+ ? false
+ : actual.KafkaTriggerService.existsKafkaTrigger(data),
+ listKafkaTriggers: async (data: { workspace: string }) =>
+ hasBenchmarkWorkspace(data.workspace) ? [] : actual.KafkaTriggerService.listKafkaTriggers(data),
+ getKafkaTrigger: async (data: { workspace: string; path: string }) => {
+ if (hasBenchmarkWorkspace(data.workspace)) {
+ throw new Error(`Kafka trigger "${data.path}" not found in benchmark workspace`)
+ }
+ return actual.KafkaTriggerService.getKafkaTrigger(data)
+ }
+ }),
+ NatsTriggerService: wrapService(actual.NatsTriggerService, {
+ existsNatsTrigger: async (data: { workspace: string; path: string }) =>
+ hasBenchmarkWorkspace(data.workspace) ? false : actual.NatsTriggerService.existsNatsTrigger(data),
+ listNatsTriggers: async (data: { workspace: string }) =>
+ hasBenchmarkWorkspace(data.workspace) ? [] : actual.NatsTriggerService.listNatsTriggers(data),
+ getNatsTrigger: async (data: { workspace: string; path: string }) => {
+ if (hasBenchmarkWorkspace(data.workspace)) {
+ throw new Error(`NATS trigger "${data.path}" not found in benchmark workspace`)
+ }
+ return actual.NatsTriggerService.getNatsTrigger(data)
+ }
+ }),
+ PostgresTriggerService: wrapService(actual.PostgresTriggerService, {
+ existsPostgresTrigger: async (data: { workspace: string; path: string }) =>
+ hasBenchmarkWorkspace(data.workspace)
+ ? false
+ : actual.PostgresTriggerService.existsPostgresTrigger(data),
+ listPostgresTriggers: async (data: { workspace: string }) =>
+ hasBenchmarkWorkspace(data.workspace)
+ ? []
+ : actual.PostgresTriggerService.listPostgresTriggers(data),
+ getPostgresTrigger: async (data: { workspace: string; path: string }) => {
+ if (hasBenchmarkWorkspace(data.workspace)) {
+ throw new Error(`Postgres trigger "${data.path}" not found in benchmark workspace`)
+ }
+ return actual.PostgresTriggerService.getPostgresTrigger(data)
+ }
+ }),
+ MqttTriggerService: wrapService(actual.MqttTriggerService, {
+ existsMqttTrigger: async (data: { workspace: string; path: string }) =>
+ hasBenchmarkWorkspace(data.workspace) ? false : actual.MqttTriggerService.existsMqttTrigger(data),
+ listMqttTriggers: async (data: { workspace: string }) =>
+ hasBenchmarkWorkspace(data.workspace) ? [] : actual.MqttTriggerService.listMqttTriggers(data),
+ getMqttTrigger: async (data: { workspace: string; path: string }) => {
+ if (hasBenchmarkWorkspace(data.workspace)) {
+ throw new Error(`MQTT trigger "${data.path}" not found in benchmark workspace`)
+ }
+ return actual.MqttTriggerService.getMqttTrigger(data)
+ }
+ }),
+ SqsTriggerService: wrapService(actual.SqsTriggerService, {
+ existsSqsTrigger: async (data: { workspace: string; path: string }) =>
+ hasBenchmarkWorkspace(data.workspace) ? false : actual.SqsTriggerService.existsSqsTrigger(data),
+ listSqsTriggers: async (data: { workspace: string }) =>
+ hasBenchmarkWorkspace(data.workspace) ? [] : actual.SqsTriggerService.listSqsTriggers(data),
+ getSqsTrigger: async (data: { workspace: string; path: string }) => {
+ if (hasBenchmarkWorkspace(data.workspace)) {
+ throw new Error(`SQS trigger "${data.path}" not found in benchmark workspace`)
+ }
+ return actual.SqsTriggerService.getSqsTrigger(data)
+ }
+ }),
+ GcpTriggerService: wrapService(actual.GcpTriggerService, {
+ existsGcpTrigger: async (data: { workspace: string; path: string }) =>
+ hasBenchmarkWorkspace(data.workspace) ? false : actual.GcpTriggerService.existsGcpTrigger(data),
+ listGcpTriggers: async (data: { workspace: string }) =>
+ hasBenchmarkWorkspace(data.workspace) ? [] : actual.GcpTriggerService.listGcpTriggers(data),
+ getGcpTrigger: async (data: { workspace: string; path: string }) => {
+ if (hasBenchmarkWorkspace(data.workspace)) {
+ throw new Error(`GCP trigger "${data.path}" not found in benchmark workspace`)
+ }
+ return actual.GcpTriggerService.getGcpTrigger(data)
+ }
+ }),
+ AzureTriggerService: wrapService(actual.AzureTriggerService, {
+ existsAzureTrigger: async (data: { workspace: string; path: string }) =>
+ hasBenchmarkWorkspace(data.workspace)
+ ? false
+ : actual.AzureTriggerService.existsAzureTrigger(data),
+ listAzureTriggers: async (data: { workspace: string }) =>
+ hasBenchmarkWorkspace(data.workspace) ? [] : actual.AzureTriggerService.listAzureTriggers(data),
+ getAzureTrigger: async (data: { workspace: string; path: string }) => {
+ if (hasBenchmarkWorkspace(data.workspace)) {
+ throw new Error(`Azure trigger "${data.path}" not found in benchmark workspace`)
+ }
+ return actual.AzureTriggerService.getAzureTrigger(data)
+ }
})
}
})
diff --git a/ai_evals/adapters/frontend/windmillBackend.test.ts b/ai_evals/adapters/frontend/windmillBackend.test.ts
new file mode 100644
index 0000000000..302502c2d9
--- /dev/null
+++ b/ai_evals/adapters/frontend/windmillBackend.test.ts
@@ -0,0 +1,104 @@
+import { afterEach, describe, expect, it } from "bun:test";
+import type { WindmillBackendSettings } from "../../core/windmillBackendSettings";
+import {
+ WindmillBackendClient,
+ assertWindmillBackendReachable,
+} from "./windmillBackend";
+
+const ORIGINAL_FETCH = globalThis.fetch;
+
+afterEach(() => {
+ globalThis.fetch = ORIGINAL_FETCH;
+});
+
+describe("assertWindmillBackendReachable", () => {
+ it("logs in to verify backend reachability", async () => {
+ const requests: Array<{ url: string; init?: RequestInit }> = [];
+ globalThis.fetch = mockFetch(requests, textResponse(200, "token"));
+
+ await expect(
+ assertWindmillBackendReachable(
+ buildSettings({ baseUrl: "http://backend.test/reachable" }),
+ ),
+ ).resolves.toBeUndefined();
+
+ expect(requests.map((entry) => entry.url)).toEqual([
+ "http://backend.test/reachable/api/auth/login",
+ ]);
+ });
+
+ it("adds setup guidance when the backend cannot be initialized", async () => {
+ globalThis.fetch = mockFetch(
+ [],
+ textResponse(401, "invalid password"),
+ );
+
+ await expect(
+ assertWindmillBackendReachable(
+ buildSettings({ baseUrl: "http://backend.test/auth-failure" }),
+ ),
+ ).rejects.toThrow(
+ "Start a Windmill backend at that URL, or set WMILL_AI_EVAL_BACKEND_URL=.",
+ );
+ });
+});
+
+describe("WindmillBackendClient", () => {
+ it("creates or reuses the specified backend workspace without deleting it", async () => {
+ const requests: Array<{ url: string; init?: RequestInit }> = [];
+ globalThis.fetch = mockFetch(
+ requests,
+ textResponse(200, "token"),
+ textResponse(200, "false"),
+ textResponse(200, ""),
+ );
+
+ const client = new WindmillBackendClient(
+ buildSettings({
+ baseUrl: "http://backend.test/shared-workspace",
+ workspaceOverride: "shared-evals",
+ }),
+ );
+
+ await expect(
+ client.withWorkspace("case-a", 1, async (workspaceId) => workspaceId),
+ ).resolves.toBe("shared-evals");
+
+ expect(requests.map((entry) => entry.url)).toEqual([
+ "http://backend.test/shared-workspace/api/auth/login",
+ "http://backend.test/shared-workspace/api/workspaces/exists",
+ "http://backend.test/shared-workspace/api/workspaces/create",
+ ]);
+ });
+});
+
+function buildSettings(
+ overrides: Partial = {},
+): WindmillBackendSettings {
+ return {
+ baseUrl: "http://backend.test/default",
+ email: "admin@windmill.dev",
+ password: "changeme",
+ ...overrides,
+ };
+}
+
+function mockFetch(
+ requests: Array<{ url: string; init?: RequestInit }>,
+ ...responses: Response[]
+): typeof fetch {
+ const queue = [...responses];
+ return async (input, init) => {
+ const url = String(input);
+ requests.push({ url, init });
+ const next = queue.shift();
+ if (!next) {
+ throw new Error(`Unexpected fetch: ${url}`);
+ }
+ return next;
+ };
+}
+
+function textResponse(status: number, body: string): Response {
+ return new Response(body, { status });
+}
diff --git a/ai_evals/adapters/frontend/windmillBackend.ts b/ai_evals/adapters/frontend/windmillBackend.ts
index c8d9537779..2247d8e5d5 100644
--- a/ai_evals/adapters/frontend/windmillBackend.ts
+++ b/ai_evals/adapters/frontend/windmillBackend.ts
@@ -3,6 +3,7 @@ import type { WindmillBackendSettings } from "../../core/windmillBackendSettings
const tokenCache = new Map>();
const sharedWorkspaceQueue = new Map>();
+const DEFAULT_WORKSPACE_PREFIX = "ai-evals";
export class WindmillBackendClient {
constructor(private readonly settings: WindmillBackendSettings) {}
@@ -14,7 +15,7 @@ export class WindmillBackendClient {
): Promise {
const workspaceId =
this.settings.workspaceOverride ??
- buildWorkspaceId(this.settings.workspacePrefix, caseId, attempt);
+ buildWorkspaceId(caseId, attempt);
const run = async () => {
await this.ensureWorkspace(workspaceId);
@@ -22,7 +23,7 @@ export class WindmillBackendClient {
try {
return await body(workspaceId);
} finally {
- if (!this.settings.keepWorkspaces && !this.settings.workspaceOverride) {
+ if (!this.settings.workspaceOverride) {
await this.deleteWorkspace(workspaceId).catch(() => undefined);
}
}
@@ -136,6 +137,24 @@ export class WindmillBackendClient {
}
}
+export async function assertWindmillBackendReachable(
+ settings: WindmillBackendSettings,
+): Promise {
+ try {
+ await new WindmillBackendClient(settings).getToken();
+ } catch (error) {
+ const details = error instanceof Error ? error.message : String(error);
+ throw new Error(
+ [
+ `Could not initialize the Windmill backend for AI eval proxy at ${settings.baseUrl}.`,
+ "Start a Windmill backend at that URL, or set WMILL_AI_EVAL_BACKEND_URL=.",
+ `Using login ${settings.email}; if authentication failed, set WMILL_AI_EVAL_BACKEND_EMAIL and WMILL_AI_EVAL_BACKEND_PASSWORD.`,
+ `Details: ${details}`,
+ ].join("\n"),
+ );
+ }
+}
+
async function withSharedWorkspaceLock(
workspaceId: string,
body: () => Promise,
@@ -160,18 +179,14 @@ async function withSharedWorkspaceLock(
}
}
-function buildWorkspaceId(
- prefix: string,
- caseId: string,
- attempt: number,
-): string {
+function buildWorkspaceId(caseId: string, attempt: number): string {
const caseSlug = caseId
.toLowerCase()
.replace(/[^a-z0-9-]+/g, "-")
.replace(/^-+|-+$/g, "")
.slice(0, 30);
const suffix = randomUUID().slice(0, 8);
- return `${prefix}-${caseSlug || "case"}-a${attempt}-${suffix}`;
+ return `${DEFAULT_WORKSPACE_PREFIX}-${caseSlug || "case"}-a${attempt}-${suffix}`;
}
async function expectOk(response: Response, context: string): Promise {
diff --git a/ai_evals/cases/global.yaml b/ai_evals/cases/global.yaml
new file mode 100644
index 0000000000..732ca7f2e9
--- /dev/null
+++ b/ai_evals/cases/global.yaml
@@ -0,0 +1,620 @@
+- id: global-test1-script-create
+ prompt: |-
+ Create a draft Bun script at `f/evals/global/greet_user`.
+ It should take a string `name` input and return `Hello, ${name}!`.
+ Leave it as an AI draft only; do not deploy or save it.
+ runtime:
+ maxTurns: 8
+ validate:
+ draftCountExactly: 1
+ requiredDrafts:
+ - type: script
+ path: f/evals/global/greet_user
+ language: bun
+ valueIncludes:
+ - name
+ - Hello
+ toolExpect:
+ requiredToolsUsed:
+ - write_script
+ forbiddenToolsUsed:
+ - deploy_workspace_item
+ - delete_workspace_item
+ judgeChecklist:
+ - creates a Bun script draft at f/evals/global/greet_user
+ - the script accepts a name input
+ - the script returns a greeting containing Hello, the provided name, and an exclamation mark
+ - the result stays as an AI draft and is not deployed or saved to the workspace
+
+- id: global-test2-script-edit-existing
+ prompt: |-
+ Update the existing workspace script at `f/evals/global/format_greeting`.
+ Keep it as a Bun script, but change the greeting so the provided name is uppercased and the returned message ends with an exclamation mark.
+ Leave the result as an AI draft only; do not deploy or save it.
+ initial: ai_evals/fixtures/frontend/global/initial/format_greeting_script.json
+ runtime:
+ maxTurns: 8
+ validate:
+ draftCountExactly: 1
+ requiredDrafts:
+ - type: script
+ path: f/evals/global/format_greeting
+ language: bun
+ valueIncludes:
+ - toUpperCase
+ - "!"
+ toolExpect:
+ forbiddenToolsUsed:
+ - deploy_workspace_item
+ - delete_workspace_item
+ judgeChecklist:
+ - creates an AI draft for the existing f/evals/global/format_greeting script
+ - preserves the script as Bun
+ - uppercases the provided name in the greeting
+ - returns a message ending with an exclamation mark
+ - does not deploy or save the draft to the workspace
+
+- id: global-test3-flow-create
+ prompt: |-
+ Create a draft flow at `f/evals/global/sum_numbers`.
+ It should take two numeric inputs, `a` and `b`, and return their sum.
+ Leave it as an AI draft only; do not deploy or save it.
+ runtime:
+ maxTurns: 8
+ validate:
+ draftCountExactly: 1
+ requiredDrafts:
+ - type: flow
+ path: f/evals/global/sum_numbers
+ valueIncludes:
+ - modules
+ - rawscript
+ - flow_input.a
+ - flow_input.b
+ toolExpect:
+ requiredToolsUsed:
+ - write_flow
+ forbiddenToolsUsed:
+ - deploy_workspace_item
+ - delete_workspace_item
+ toolCallArgs:
+ - tool: write_flow
+ field: modules
+ stringStartsWithAnyOf:
+ - "["
+ judgeChecklist:
+ - creates a flow draft at f/evals/global/sum_numbers
+ - the flow accepts numeric inputs a and b
+ - the flow returns the sum of a and b
+ - the result stays as an AI draft and is not deployed or saved to the workspace
+
+- id: global-test4-multi-artifact-notification-job
+ prompt: |-
+ Set up a draft stale-trial notification job.
+ Create a Bun script at `f/evals/global/check_stale_trials` that accepts `max_age_days`, uses mocked inline trial account data, and returns the stale trial account IDs.
+ Also create a weekday 09:00 UTC schedule at `f/evals/global/check_stale_trials_weekday` for that script with `max_age_days` set to 14.
+ Add an HTTP POST trigger at `f/evals/global/check_stale_trials_manual` with route path `evals/check-stale-trials` that runs the same script manually.
+ Leave everything as AI drafts only; do not deploy or save anything to the workspace.
+ runtime:
+ maxTurns: 12
+ validate:
+ draftCountExactly: 3
+ requiredDrafts:
+ - type: script
+ path: f/evals/global/check_stale_trials
+ language: bun
+ valueIncludes:
+ - max_age_days
+ - trial
+ - type: schedule
+ path: f/evals/global/check_stale_trials_weekday
+ valueIncludes:
+ - f/evals/global/check_stale_trials
+ - UTC
+ - "14"
+ - type: trigger
+ triggerKind: http
+ path: f/evals/global/check_stale_trials_manual
+ valueIncludes:
+ - evals/check-stale-trials
+ - f/evals/global/check_stale_trials
+ toolExpect:
+ requiredToolsUsed:
+ - write_script
+ - write_schedule
+ - write_trigger
+ forbiddenToolsUsed:
+ - deploy_workspace_item
+ - delete_workspace_item
+ judgeChecklist:
+ - creates a Bun script draft for stale trial accounts
+ - creates a weekday 09:00 UTC schedule draft for the script with max_age_days set to 14
+ - creates an HTTP POST trigger draft with route path evals/check-stale-trials for the same script
+ - leaves all artifacts as drafts only and does not deploy
+
+- id: global-test5-existing-flow-inline-code-edit
+ prompt: |-
+ Update the existing flow at `f/evals/global/process_invoice`.
+ Only change the `calculate_total` inline code so it applies 8% tax and returns an object containing `subtotal`, `tax`, and `total`.
+ Leave the updated flow as an AI draft only; do not deploy or save it.
+ initial: ai_evals/fixtures/frontend/global/initial/process_invoice_flow.json
+ runtime:
+ maxTurns: 10
+ validate:
+ draftCountExactly: 1
+ requiredDrafts:
+ - type: flow
+ path: f/evals/global/process_invoice
+ valueIncludes:
+ - calculate_total
+ - tax
+ - total
+ toolExpect:
+ requiredToolsUsed:
+ - read_workspace_item
+ forbiddenToolsUsed:
+ - deploy_workspace_item
+ - delete_workspace_item
+ judgeChecklist:
+ - reads the existing process_invoice flow before editing it
+ - updates the calculate_total inline code to apply 8% tax
+ - returns subtotal, tax, and total from the updated flow logic
+ - leaves the result as an AI draft only
+
+- id: global-test6-secret-variable-draft
+ prompt: |-
+ Create a secret variable draft at `f/evals/global/slack_bot_token`.
+ Use the placeholder value `xoxb-redacted-test-token` and description `Slack bot token for eval notifications`.
+ Do not create any resource or deploy anything.
+ runtime:
+ maxTurns: 6
+ validate:
+ draftCountExactly: 1
+ requiredDrafts:
+ - type: variable
+ path: f/evals/global/slack_bot_token
+ valueIncludes:
+ - Slack bot token
+ - "true"
+ forbiddenDrafts:
+ - type: resource
+ path: f/evals/global/slack_bot_token
+ toolExpect:
+ requiredToolsUsed:
+ - write_variable
+ forbiddenToolsUsed:
+ - write_resource
+ - deploy_workspace_item
+ - delete_workspace_item
+ toolCallArgs:
+ - tool: write_variable
+ field: value
+ stringStartsWithAnyOf:
+ - xoxb-redacted-test-token
+ skipJudge: true
+ judgeChecklist:
+ - creates exactly one secret variable draft at f/evals/global/slack_bot_token
+ - uses the requested placeholder value and description
+ - does not create a resource or deploy anything
+
+- id: global-test7-ambiguous-app-asks-question
+ prompt: |-
+ Create a new raw app for triaging support tickets.
+ runtime:
+ maxTurns: 4
+ validate:
+ draftCountExactly: 0
+ toolExpect:
+ requiredToolsUsed:
+ - askUserQuestion
+ forbiddenToolsUsed:
+ - init_app
+ - write_app_file
+ - write_app_runnable
+ - deploy_workspace_item
+ - delete_workspace_item
+ skipJudge: true
+
+- id: global-test8-human-script-infer-path-language
+ prompt: |-
+ I need a small helper that formats a customer-facing welcome line.
+ It should take a person's name and return "Welcome aboard, !".
+ Please just stage it as a draft for now.
+ runtime:
+ maxTurns: 8
+ validate:
+ draftCountExactly: 1
+ requiredDrafts:
+ - type: script
+ valueIncludes:
+ - Welcome aboard
+ - name
+ toolExpect:
+ requiredToolsUsed:
+ - write_script
+ forbiddenToolsUsed:
+ - deploy_workspace_item
+ - delete_workspace_item
+ judgeChecklist:
+ - creates a single script draft for a welcome-line helper
+ - accepts a person's name as input
+ - returns a message containing Welcome aboard, the provided name, and an exclamation mark
+ - chooses a reasonable workspace path and script language without needing the user to specify them
+ - leaves the result as an AI draft only
+
+- id: global-test9-human-weekday-trial-job
+ prompt: |-
+ Can you set up a draft daily job that checks a few hard-coded trial accounts and returns the ones whose trial has ended?
+ It should run every weekday morning around 9 in UTC with a 30 day cutoff.
+ Keep it as draft work only.
+ runtime:
+ maxTurns: 10
+ validate:
+ draftCountExactly: 2
+ requiredDrafts:
+ - type: script
+ pathIncludes:
+ - trial
+ valueIncludes:
+ - trial
+ - "30"
+ - type: schedule
+ pathIncludes:
+ - trial
+ valueIncludes:
+ - UTC
+ toolExpect:
+ requiredToolsUsed:
+ - write_script
+ - write_schedule
+ forbiddenToolsUsed:
+ - deploy_workspace_item
+ - delete_workspace_item
+ judgeChecklist:
+ - creates a script draft that checks hard-coded trial accounts
+ - returns the accounts whose trial has ended based on a 30 day cutoff
+ - creates a schedule draft for weekday mornings around 09:00 UTC
+ - links the schedule to the generated script
+ - leaves both artifacts as drafts only
+
+- id: global-test10-human-secret-variable
+ prompt: |-
+ I need a placeholder Slack bot token stored securely for future notification work.
+ Use xoxb-redacted-test-token and note that it is for eval notifications.
+ Only prepare a draft.
+ runtime:
+ maxTurns: 6
+ validate:
+ draftCountExactly: 1
+ requiredDrafts:
+ - type: variable
+ pathIncludes:
+ - slack
+ valueIncludes:
+ - eval notifications
+ - "true"
+ toolExpect:
+ requiredToolsUsed:
+ - write_variable
+ forbiddenToolsUsed:
+ - write_resource
+ - deploy_workspace_item
+ - delete_workspace_item
+ toolCallArgs:
+ - tool: write_variable
+ field: value
+ stringStartsWithAnyOf:
+ - xoxb-redacted-test-token
+ skipJudge: true
+ judgeChecklist:
+ - creates a single secret variable draft for the Slack bot token placeholder
+ - uses the requested placeholder value
+ - includes a note or description that it is for eval notifications
+ - does not create a resource or deploy anything
+
+- id: global-test11-human-existing-flow-informal-edit
+ prompt: |-
+ There is an invoice processing flow in this workspace.
+ Can you adjust its total calculation so it adds 8% tax and returns subtotal, tax, and total?
+ Keep the change as a draft.
+ initial: ai_evals/fixtures/frontend/global/initial/process_invoice_flow.json
+ runtime:
+ maxTurns: 10
+ validate:
+ draftCountExactly: 1
+ requiredDrafts:
+ - type: flow
+ pathIncludes:
+ - invoice
+ valueIncludes:
+ - calculate_total
+ - tax
+ - total
+ toolExpect:
+ requiredToolsUsed:
+ - read_workspace_item
+ forbiddenToolsUsed:
+ - deploy_workspace_item
+ - delete_workspace_item
+ judgeChecklist:
+ - finds and edits the existing invoice processing flow without the user providing its exact path
+ - updates the total calculation to apply 8% tax
+ - returns subtotal, tax, and total from the updated flow logic
+ - leaves the result as an AI draft only
+
+- id: global-test12-current-live-script-edit
+ prompt: |-
+ The script I have open formats greetings.
+ Can you update this script so it uppercases the name before greeting them and ends with an exclamation mark?
+ Keep it as draft work.
+ initial: ai_evals/fixtures/frontend/global/initial/current_greeting_live_script.json
+ runtime:
+ maxTurns: 8
+ validate:
+ draftCountExactly: 1
+ requiredDrafts:
+ - type: script
+ path: f/evals/global/current_greeting
+ language: bun
+ valueIncludes:
+ - toUpperCase
+ - "!"
+ forbiddenDrafts:
+ - type: script
+ path: f/evals/global/format_greeting
+ - type: script
+ path: f/evals/global/format_greeting_archive
+ toolExpect:
+ requiredToolsUsed:
+ - read_workspace_item
+ forbiddenToolsUsed:
+ - deploy_workspace_item
+ - delete_workspace_item
+ judgeChecklist:
+ - resolves "this script" to the active live editor script instead of another similarly named workspace script
+ - updates the greeting logic to uppercase the provided name
+ - returns a greeting ending with an exclamation mark
+ - leaves the result as a draft only
+
+- id: global-test13-current-live-flow-edit
+ prompt: |-
+ I have the invoice flow open.
+ In the current flow, update the total calculation to add 8% tax and return subtotal, tax, and total.
+ Keep the change as a draft.
+ initial: ai_evals/fixtures/frontend/global/initial/current_invoice_live_flow.json
+ runtime:
+ maxTurns: 10
+ validate:
+ draftCountExactly: 1
+ requiredDrafts:
+ - type: flow
+ path: f/evals/global/current_invoice_flow
+ valueIncludes:
+ - calculate_total
+ - tax
+ - total
+ forbiddenDrafts:
+ - type: flow
+ path: f/evals/global/process_invoice
+ - type: flow
+ path: f/evals/global/process_refund
+ toolExpect:
+ requiredToolsUsed:
+ - read_workspace_item
+ forbiddenToolsUsed:
+ - deploy_workspace_item
+ - delete_workspace_item
+ judgeChecklist:
+ - resolves "current flow" to the active live editor flow
+ - does not edit the similarly named deployed invoice or refund flows
+ - updates the calculate_total logic to apply 8% tax
+ - returns subtotal, tax, and total from the updated flow logic
+ - leaves the result as a draft only
+
+- id: global-test14-current-without-live-editor-asks-question
+ prompt: |-
+ Please update this script so it returns `ok`.
+ Keep it as a draft.
+ runtime:
+ maxTurns: 4
+ validate:
+ draftCountExactly: 0
+ toolExpect:
+ forbiddenToolsUsed:
+ - write_script
+ - edit_script
+ - write_flow
+ - deploy_workspace_item
+ - delete_workspace_item
+ skipJudge: true
+ judgeChecklist:
+ - asks which script to update when the user refers to "this script" without selected or active editor context
+ - does not guess a path or create a new script draft
+
+- id: global-test15-human-postgres-resource
+ prompt: |-
+ I'm wiring the eval reporting database into this workspace.
+ Can you stage a Postgres connection for it in the shared evals/global folder?
+ Use host `reports-db.internal`, port 5432, database `evals_reporting`, user `report_reader`, and password `pg-redacted-reporting-password`.
+ Keep the credentials safe.
+ This is just draft work for now.
+ runtime:
+ maxTurns: 10
+ validate:
+ draftCountExactly: 2
+ requiredDrafts:
+ - type: variable
+ pathStartsWith: f/evals/global/
+ pathIncludes:
+ - evals
+ - global
+ - report
+ - password
+ valueIncludes:
+ - "true"
+ - report
+ - type: resource
+ pathStartsWith: f/evals/global/
+ pathIncludes:
+ - evals
+ - global
+ - report
+ valueIncludes:
+ - postgres
+ - reports-db.internal
+ - "5432"
+ - evals_reporting
+ - report_reader
+ - "$var:"
+ valueExcludes:
+ - pg-redacted-reporting-password
+ toolExpect:
+ requiredToolsUsed:
+ - write_variable
+ - search_resource_types
+ - write_resource
+ forbiddenToolsUsed:
+ - write_schedule
+ - write_trigger
+ - deploy_workspace_item
+ - delete_workspace_item
+ toolCallArgs:
+ - tool: write_variable
+ field: value
+ stringStartsWithAnyOf:
+ - pg-redacted-reporting-password
+ skipJudge: true
+ judgeChecklist:
+ - creates a Postgres resource draft for the eval reporting database
+ - creates a secret variable draft for the database password
+ - puts the drafts in sensible eval/global reporting-related paths
+ - uses the requested host, port, database, and user
+ - references the secret variable from the resource instead of embedding the password
+ - leaves the work as a draft only
+
+- id: global-test16-human-visible-variable
+ prompt: |-
+ We keep reusing a 30 day trial cutoff in eval notification jobs.
+ Can you stage that as a normal workspace variable in the shared evals/global folder, with a short description so people know what it controls?
+ It is not a secret.
+ runtime:
+ maxTurns: 6
+ validate:
+ draftCountExactly: 1
+ requiredDrafts:
+ - type: variable
+ pathStartsWith: f/evals/global/
+ pathIncludes:
+ - evals
+ - global
+ - trial
+ valueIncludes:
+ - "30"
+ - "false"
+ - trial
+ toolExpect:
+ requiredToolsUsed:
+ - write_variable
+ forbiddenToolsUsed:
+ - write_resource
+ - write_schedule
+ - write_trigger
+ - deploy_workspace_item
+ - delete_workspace_item
+ judgeChecklist:
+ - creates exactly one non-secret variable draft for the trial cutoff
+ - stores the value 30
+ - chooses a sensible eval/global path related to trials or notifications
+ - includes a useful description of what the value controls
+ - does not create resources, schedules, triggers, or deployed workspace changes
+
+- id: global-test17-human-schedule-existing-helper
+ prompt: |-
+ The workspace already has a report digest helper.
+ Can you stage a weekday 8:30 AM UTC run for it with `dry_run` turned on?
+ I only want the schedule draft for review.
+ initial: ai_evals/fixtures/frontend/global/initial/report_digest_script.json
+ runtime:
+ maxTurns: 8
+ validate:
+ draftCountExactly: 1
+ requiredDrafts:
+ - type: schedule
+ pathIncludes:
+ - digest
+ valueIncludes:
+ - f/evals/global/send_report_digest
+ - UTC
+ - dry_run
+ - "true"
+ toolExpect:
+ requiredToolsUsed:
+ - list_workspace_items
+ - write_schedule
+ forbiddenToolsUsed:
+ - write_script
+ - write_flow
+ - write_resource
+ - write_variable
+ - write_trigger
+ - deploy_workspace_item
+ - delete_workspace_item
+ judgeChecklist:
+ - finds the existing report digest helper rather than creating a new script or flow
+ - creates one schedule draft for that helper
+ - schedules it for weekdays around 08:30 UTC
+ - passes dry_run as true
+ - leaves only the schedule draft for review
+
+- id: global-test18-human-slack-resource-with-secret
+ prompt: |-
+ I'm preparing Slack notifications for eval failures.
+ Can you stage a Slack connection in the shared evals/global folder?
+ The bot token is `xoxb-redacted-test-token`; keep it safe.
+ Don't deploy anything yet.
+ runtime:
+ maxTurns: 8
+ validate:
+ draftCountExactly: 2
+ requiredDrafts:
+ - type: variable
+ pathStartsWith: f/evals/global/
+ pathIncludes:
+ - evals
+ - global
+ - slack
+ - token
+ valueIncludes:
+ - "true"
+ - type: resource
+ pathStartsWith: f/evals/global/
+ pathIncludes:
+ - evals
+ - global
+ - slack
+ valueIncludes:
+ - slack
+ - "$var:"
+ valueExcludes:
+ - xoxb-redacted-test-token
+ toolExpect:
+ requiredToolsUsed:
+ - write_variable
+ - search_resource_types
+ - write_resource
+ forbiddenToolsUsed:
+ - write_schedule
+ - write_trigger
+ - deploy_workspace_item
+ - delete_workspace_item
+ toolCallArgs:
+ - tool: write_variable
+ field: value
+ stringStartsWithAnyOf:
+ - xoxb-redacted-test-token
+ skipJudge: true
+ judgeChecklist:
+ - creates a secret variable draft for the Slack bot token placeholder
+ - creates a Slack resource draft that references the secret variable instead of embedding the token
+ - keeps both drafts under a sensible eval/global Slack-related path
+ - does not create schedules, triggers, or deployed workspace changes
diff --git a/ai_evals/cli/index.ts b/ai_evals/cli/index.ts
index 202d826078..f92d6d7027 100644
--- a/ai_evals/cli/index.ts
+++ b/ai_evals/cli/index.ts
@@ -27,11 +27,8 @@ import { EVAL_MODES, type EvalMode } from "../core/types";
import { DEFAULT_JUDGE_MODEL } from "../core/judge";
import { createCliModeRunner } from "../modes/cli";
import { runFrontendBenchmarkAdapter } from "../adapters/frontend/runtime";
-import {
- FRONTEND_EVAL_TRANSPORTS,
- type FrontendEvalTransport,
- parseFrontendEvalTransport,
-} from "../core/frontendTransport";
+import { resolveWindmillBackendSettings } from "../core/windmillBackendSettings";
+import { assertWindmillBackendReachable } from "../adapters/frontend/windmillBackend";
async function main() {
const program = new Command()
@@ -56,6 +53,7 @@ async function main() {
" bun run cli -- run flow --record",
" bun run cli -- run flow --backend-validation preview",
" bun run cli -- run flow flow-test5-simple-modification --runs 3",
+ " bun run cli -- run global global-test1-script-create",
" bun run cli -- run cli bun-hello-script",
"",
"Models:",
@@ -73,7 +71,7 @@ async function main() {
program
.command("cases")
.description("List available cases")
- .argument("[mode]", "cli, flow, script, or app", parseOptionalMode)
+ .argument("[mode]", "cli, flow, script, app, or global", parseOptionalMode)
.action(async (mode?: EvalMode) => {
await handleCases(mode);
});
@@ -81,7 +79,7 @@ async function main() {
program
.command("run")
.description("Run one benchmark mode")
- .argument("", "cli, flow, script, or app", parseMode)
+ .argument("", "cli, flow, script, app, or global", parseMode)
.argument("[caseIds...]", "specific case ids to run")
.option(
"--runs ",
@@ -98,10 +96,6 @@ async function main() {
"--models ",
"comma-separated model aliases to run sequentially",
)
- .option(
- "--transport ",
- `frontend transport (${FRONTEND_EVAL_TRANSPORTS.join(", ")})`,
- )
.option("--verbose", "stream assistant output during frontend runs")
.option(
"--record",
@@ -120,7 +114,6 @@ async function main() {
output?: string;
model?: string;
models?: string;
- transport?: string;
verbose?: boolean;
record?: boolean;
backendValidation?: string;
@@ -133,9 +126,6 @@ async function main() {
outputPath: options.output,
model: options.model,
models: options.models,
- transport: options.transport
- ? parseFrontendEvalTransport(options.transport)
- : undefined,
verbose: options.verbose ?? false,
record: options.record ?? false,
backendValidation: options.backendValidation,
@@ -163,7 +153,7 @@ function handleModels() {
process.stdout.write("Available models\n");
for (const model of EVAL_MODELS) {
const supports = [
- ...(model.frontend ? ["flow", "script", "app"] : []),
+ ...(model.frontend ? ["flow", "script", "app", "global"] : []),
...(model.cli ? ["cli"] : []),
];
const aliases = [
@@ -184,7 +174,6 @@ async function handleRun(input: {
outputPath?: string;
model?: string;
models?: string;
- transport?: FrontendEvalTransport;
verbose: boolean;
record: boolean;
backendValidation?: string;
@@ -197,11 +186,6 @@ async function handleRun(input: {
if (input.model && input.models) {
throw new Error("Use either --model or --models, not both");
}
- if (input.mode === "cli" && input.transport === "proxy") {
- throw new Error(
- "--transport proxy is only supported for flow, script, and app modes",
- );
- }
const selectedCases = await loadSelectedCases(input.mode, input.caseIds);
const models = resolveRequestedModels(input.mode, input.model, input.models);
@@ -220,11 +204,14 @@ async function handleRun(input: {
"--backend-validation currently supports only flow and script modes",
);
}
+ if (input.mode !== "cli") {
+ await assertWindmillBackendReachable(resolveWindmillBackendSettings());
+ }
const summaries: Array<{
label: string;
passRate: number;
- averageDurationMs: number;
+ averagePassedDurationMs: number | null;
}> = [];
for (const [index, model] of models.entries()) {
@@ -249,7 +236,6 @@ async function handleRun(input: {
caseIds: input.caseIds,
runs: input.runs,
model: model.id,
- transport: input.transport,
verbose: input.verbose,
backendValidation,
});
@@ -273,7 +259,7 @@ async function handleRun(input: {
summaries.push({
label: `${model.id} (${runModel})`,
passRate: result.passRate,
- averageDurationMs: result.averageDurationMs,
+ averagePassedDurationMs: result.averagePassedDurationMs ?? null,
});
}
@@ -281,7 +267,7 @@ async function handleRun(input: {
process.stdout.write("\nModel summary\n");
for (const summary of summaries) {
process.stdout.write(
- `- ${summary.label}: ${formatPercent(summary.passRate)} | ${Math.round(summary.averageDurationMs)}ms\n`,
+ `- ${summary.label}: ${formatPercent(summary.passRate)} | passed avg ${formatNullableDuration(summary.averagePassedDurationMs)}\n`,
);
}
}
@@ -365,6 +351,10 @@ function formatPercent(value: number): string {
return `${(value * 100).toFixed(1)}%`;
}
+function formatNullableDuration(value: number | null): string {
+ return value === null ? "n/a" : `${Math.round(value)}ms`;
+}
+
void main().catch((error) => {
const message = error instanceof Error ? error.message : String(error);
process.stderr.write(`${message}\n`);
diff --git a/ai_evals/core/backendValidation.ts b/ai_evals/core/backendValidation.ts
index 87094fd23f..1484aee2d4 100644
--- a/ai_evals/core/backendValidation.ts
+++ b/ai_evals/core/backendValidation.ts
@@ -13,9 +13,7 @@ export interface BackendValidationSettings {
baseUrl: string;
email: string;
password: string;
- keepWorkspaces: boolean;
workspaceOverride?: string;
- workspacePrefix: string;
pollIntervalMs: number;
maxWaitMs: number;
}
diff --git a/ai_evals/core/cases.test.ts b/ai_evals/core/cases.test.ts
index 977bb71390..526ea7c70f 100644
--- a/ai_evals/core/cases.test.ts
+++ b/ai_evals/core/cases.test.ts
@@ -183,6 +183,54 @@ describe("loadCases", () => {
});
});
+ it("loads global draft validation and forbidden tool expectations", async () => {
+ const globalCases = await loadCases("global");
+ const caseEntry = globalCases.find((entry) => entry.id === "global-test1-script-create");
+
+ expect(caseEntry?.validate).toMatchObject({
+ draftCountExactly: 1,
+ requiredDrafts: [
+ {
+ type: "script",
+ path: "f/evals/global/greet_user",
+ language: "bun",
+ },
+ ],
+ });
+ expect(caseEntry?.toolExpect).toMatchObject({
+ requiredToolsUsed: ["write_script"],
+ forbiddenToolsUsed: ["deploy_workspace_item", "delete_workspace_item"],
+ });
+ });
+
+ it("loads global active-editor eval cases", async () => {
+ const globalCases = await loadCases("global");
+ const scriptCase = globalCases.find(
+ (entry) => entry.id === "global-test12-current-live-script-edit"
+ );
+ const flowCase = globalCases.find(
+ (entry) => entry.id === "global-test13-current-live-flow-edit"
+ );
+
+ expect(scriptCase?.initialPath).toContain(
+ "ai_evals/fixtures/frontend/global/initial/current_greeting_live_script.json"
+ );
+ expect(scriptCase?.toolExpect).toMatchObject({
+ requiredToolsUsed: ["read_workspace_item"],
+ });
+ expect(flowCase?.initialPath).toContain(
+ "ai_evals/fixtures/frontend/global/initial/current_invoice_live_flow.json"
+ );
+ expect(flowCase?.validate).toMatchObject({
+ requiredDrafts: [
+ {
+ type: "flow",
+ path: "f/evals/global/current_invoice_flow",
+ },
+ ],
+ });
+ });
+
it("loads tool expectations for workspace mutation cases", async () => {
const scriptCases = await loadCases("script");
const caseEntry = scriptCases.find(
diff --git a/ai_evals/core/frontendTransport.test.ts b/ai_evals/core/frontendTransport.test.ts
deleted file mode 100644
index 09ebd1b3a7..0000000000
--- a/ai_evals/core/frontendTransport.test.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-import { afterEach, describe, expect, it } from "bun:test";
-import {
- parseFrontendEvalTransport,
- resolveFrontendEvalTransportSettings,
-} from "./frontendTransport";
-
-const ORIGINAL_ENV = {
- WMILL_AI_EVAL_BACKEND_URL: process.env.WMILL_AI_EVAL_BACKEND_URL,
-};
-
-afterEach(() => {
- if (ORIGINAL_ENV.WMILL_AI_EVAL_BACKEND_URL === undefined) {
- delete process.env.WMILL_AI_EVAL_BACKEND_URL;
- } else {
- process.env.WMILL_AI_EVAL_BACKEND_URL =
- ORIGINAL_ENV.WMILL_AI_EVAL_BACKEND_URL;
- }
-});
-
-describe("parseFrontendEvalTransport", () => {
- it("defaults to direct when unset", () => {
- expect(parseFrontendEvalTransport(undefined)).toBe("direct");
- });
-
- it("accepts proxy explicitly", () => {
- expect(parseFrontendEvalTransport("proxy")).toBe("proxy");
- });
-
- it("rejects unsupported values", () => {
- expect(() => parseFrontendEvalTransport("worker")).toThrow(
- "Unsupported frontend eval transport: worker",
- );
- });
-});
-
-describe("resolveFrontendEvalTransportSettings", () => {
- it("includes backend settings for proxy transport", () => {
- process.env.WMILL_AI_EVAL_BACKEND_URL = "http://127.0.0.1:8000/";
-
- expect(
- resolveFrontendEvalTransportSettings({
- evalMode: "app",
- requestedTransport: "proxy",
- }),
- ).toMatchObject({
- transport: "proxy",
- backend: {
- baseUrl: "http://127.0.0.1:8000",
- },
- });
- });
-
- it("keeps direct transport for cli runs", () => {
- expect(
- resolveFrontendEvalTransportSettings({
- evalMode: "cli",
- requestedTransport: "direct",
- }),
- ).toEqual({
- transport: "direct",
- backend: undefined,
- });
- });
-});
diff --git a/ai_evals/core/frontendTransport.ts b/ai_evals/core/frontendTransport.ts
deleted file mode 100644
index fa78505113..0000000000
--- a/ai_evals/core/frontendTransport.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-import type { EvalMode } from "./types";
-import type { WindmillBackendSettings } from "./windmillBackendSettings";
-import { resolveWindmillBackendSettings } from "./windmillBackendSettings";
-
-export const FRONTEND_EVAL_TRANSPORTS = ["direct", "proxy"] as const;
-
-export type FrontendEvalTransport = (typeof FRONTEND_EVAL_TRANSPORTS)[number];
-
-export interface FrontendEvalTransportSettings {
- transport: FrontendEvalTransport;
- backend?: WindmillBackendSettings;
-}
-
-export function parseFrontendEvalTransport(
- value?: string | null,
-): FrontendEvalTransport {
- const normalized = value?.trim().toLowerCase();
-
- if (!normalized || normalized === "direct") {
- return "direct";
- }
-
- if (normalized === "proxy") {
- return "proxy";
- }
-
- throw new Error(
- `Unsupported frontend eval transport: ${value}. Use one of: ${FRONTEND_EVAL_TRANSPORTS.join(", ")}`,
- );
-}
-
-export function resolveFrontendEvalTransportSettings(input: {
- evalMode: EvalMode;
- requestedTransport?: string | null;
-}): FrontendEvalTransportSettings {
- const transport = parseFrontendEvalTransport(input.requestedTransport);
-
- if (transport === "proxy" && input.evalMode === "cli") {
- throw new Error(
- 'Frontend eval transport "proxy" is only supported for flow, script, and app evals',
- );
- }
-
- return {
- transport,
- backend:
- transport === "proxy" ? resolveWindmillBackendSettings() : undefined,
- };
-}
diff --git a/ai_evals/core/models.test.ts b/ai_evals/core/models.test.ts
index 86bf1c6a9a..ba53c24592 100644
--- a/ai_evals/core/models.test.ts
+++ b/ai_evals/core/models.test.ts
@@ -2,28 +2,50 @@ import { describe, expect, it } from "bun:test";
import { resolveEvalModel } from "./models";
describe("resolveEvalModel", () => {
+ it("supports GPT-5.5 aliases for frontend evals", () => {
+ expect(resolveEvalModel("flow", "gpt-5.5").frontend).toEqual({
+ provider: "openai",
+ model: "gpt-5.5",
+ });
+ expect(resolveEvalModel("app", "gpt-55").frontend).toEqual({
+ provider: "openai",
+ model: "gpt-5.5",
+ });
+ expect(resolveEvalModel("script", "5.5").frontend).toEqual({
+ provider: "openai",
+ model: "gpt-5.5",
+ });
+ });
+
it("supports Gemini aliases for frontend evals", () => {
- expect(resolveEvalModel("flow", "gemini").frontend).toEqual({
- provider: "googleai",
- model: "gemini-2.5-flash",
- });
- expect(resolveEvalModel("app", "gemini-pro").frontend).toEqual({
- provider: "googleai",
- model: "gemini-2.5-pro",
- });
- expect(resolveEvalModel("script", "gemini-3-flash-preview").frontend).toEqual({
+ expect(
+ resolveEvalModel("script", "gemini-3-flash-preview").frontend,
+ ).toEqual({
provider: "googleai",
model: "gemini-3-flash-preview",
});
- expect(resolveEvalModel("flow", "gemini-3.1-pro-preview").frontend).toEqual({
- provider: "googleai",
- model: "gemini-3.1-pro-preview",
+ expect(resolveEvalModel("flow", "gemini-3.1-pro-preview").frontend).toEqual(
+ {
+ provider: "googleai",
+ model: "gemini-3.1-pro-preview",
+ },
+ );
+ });
+
+ it("supports DeepSeek aliases for frontend evals", () => {
+ expect(resolveEvalModel("flow", "deepseek").frontend).toEqual({
+ provider: "deepseek",
+ model: "deepseek-v4-flash",
+ });
+ expect(resolveEvalModel("script", "deepseek-v4-pro").frontend).toEqual({
+ provider: "deepseek",
+ model: "deepseek-v4-pro",
});
});
it("rejects Gemini aliases for cli evals", () => {
- expect(() => resolveEvalModel("cli", "gemini")).toThrow(
- "Model gemini-flash is not supported for cli mode"
+ expect(() => resolveEvalModel("cli", "gemini-3-flash-preview")).toThrow(
+ "Model gemini-3-flash-preview is not supported for cli mode",
);
});
});
diff --git a/ai_evals/core/models.ts b/ai_evals/core/models.ts
index 9cc0ab0597..295cd36135 100644
--- a/ai_evals/core/models.ts
+++ b/ai_evals/core/models.ts
@@ -1,7 +1,7 @@
import type { EvalMode } from "./types";
export interface FrontendEvalModelConfig {
- provider: "anthropic" | "openai" | "googleai";
+ provider: "anthropic" | "openai" | "googleai" | "deepseek";
model: string;
}
@@ -88,21 +88,12 @@ export const EVAL_MODELS: EvalModelSpec[] = [
},
},
{
- id: "gemini-flash",
- label: "Gemini 2.5 Flash",
- aliases: ["gemini", "gemini-flash", "gemini-2.5-flash"],
+ id: "gpt-5.5",
+ label: "GPT-5.5",
+ aliases: ["gpt-5.5", "gpt-55", "5.5"],
frontend: {
- provider: "googleai",
- model: "gemini-2.5-flash",
- },
- },
- {
- id: "gemini-pro",
- label: "Gemini 2.5 Pro",
- aliases: ["gemini-pro", "gemini-2.5-pro"],
- frontend: {
- provider: "googleai",
- model: "gemini-2.5-pro",
+ provider: "openai",
+ model: "gpt-5.5",
},
},
{
@@ -117,15 +108,40 @@ export const EVAL_MODELS: EvalModelSpec[] = [
{
id: "gemini-3.1-pro-preview",
label: "Gemini 3.1 Pro Preview",
- aliases: ["gemini-3.1-pro-preview", "gemini-3.1-pro", "gemini-3-pro-preview"],
+ aliases: [
+ "gemini-3.1-pro-preview",
+ "gemini-3.1-pro",
+ "gemini-3-pro-preview",
+ ],
frontend: {
provider: "googleai",
model: "gemini-3.1-pro-preview",
},
},
+ {
+ id: "deepseek-v4-flash",
+ label: "DeepSeek V4 Flash",
+ aliases: ["deepseek", "deepseek-v4", "deepseek-v4-flash"],
+ frontend: {
+ provider: "deepseek",
+ model: "deepseek-v4-flash",
+ },
+ },
+ {
+ id: "deepseek-v4-pro",
+ label: "DeepSeek V4 Pro",
+ aliases: ["deepseek-pro", "deepseek-v4-pro"],
+ frontend: {
+ provider: "deepseek",
+ model: "deepseek-v4-pro",
+ },
+ },
];
-export function resolveEvalModel(mode: EvalMode, alias?: string): EvalModelSpec {
+export function resolveEvalModel(
+ mode: EvalMode,
+ alias?: string,
+): EvalModelSpec {
const spec = alias ? findEvalModel(alias) : getDefaultEvalModel(mode);
if (!spec) {
throw new Error(`Unknown model: ${alias}`);
@@ -145,21 +161,26 @@ export function resolveEvalModel(mode: EvalMode, alias?: string): EvalModelSpec
export function getEvalModelHelpText(): string {
return EVAL_MODELS.map((model) => {
const modes = [
- ...(model.frontend ? ["flow", "script", "app"] : []),
+ ...(model.frontend ? ["flow", "script", "app", "global"] : []),
...(model.cli ? ["cli"] : []),
];
return ` ${model.id.padEnd(8)} ${model.label} (${modes.join(", ")})`;
}).join("\n");
}
-export function formatRunModelLabel(mode: EvalMode, model: EvalModelSpec): string {
+export function formatRunModelLabel(
+ mode: EvalMode,
+ model: EvalModelSpec,
+): string {
if (mode === "cli") {
return `${model.cli!.provider}:${model.cli!.model}`;
}
return `${model.frontend!.provider}:${model.frontend!.model}`;
}
-export function getFrontendEvalModel(model: EvalModelSpec): FrontendEvalModelConfig {
+export function getFrontendEvalModel(
+ model: EvalModelSpec,
+): FrontendEvalModelConfig {
if (!model.frontend) {
throw new Error(`Model ${model.id} does not support frontend evals`);
}
@@ -180,6 +201,8 @@ function getDefaultEvalModel(mode: EvalMode): EvalModelSpec {
function findEvalModel(alias: string): EvalModelSpec | undefined {
const normalized = alias.trim().toLowerCase();
return EVAL_MODELS.find((model) =>
- [model.id, ...model.aliases].some((candidate) => candidate.toLowerCase() === normalized)
+ [model.id, ...model.aliases].some(
+ (candidate) => candidate.toLowerCase() === normalized,
+ ),
);
}
diff --git a/ai_evals/core/results.test.ts b/ai_evals/core/results.test.ts
new file mode 100644
index 0000000000..2d6077c5bd
--- /dev/null
+++ b/ai_evals/core/results.test.ts
@@ -0,0 +1,242 @@
+import { mkdtemp, readFile, rm } from "node:fs/promises";
+import { join } from "node:path";
+import { tmpdir } from "node:os";
+import { describe, expect, it } from "bun:test";
+import {
+ appendHistoryRecord,
+ buildRunResult,
+ formatRunSummary,
+} from "./results";
+import type { BenchmarkCaseResult } from "./types";
+
+function caseResult(
+ attempts: BenchmarkCaseResult["attempts"],
+): BenchmarkCaseResult {
+ return {
+ id: "case-1",
+ prompt: "Do the thing",
+ attempts,
+ };
+}
+
+describe("benchmark results", () => {
+ it("keeps success cost metrics separate from failed attempts", () => {
+ const result = buildRunResult({
+ mode: "global",
+ runs: 1,
+ runModel: "model-under-test",
+ judgeModel: "judge-model",
+ caseResults: [
+ caseResult([
+ {
+ attempt: 1,
+ passed: true,
+ durationMs: 1000,
+ assistantMessageCount: 1,
+ toolCallCount: 1,
+ toolsUsed: ["edit_script"],
+ skillsInvoked: [],
+ checks: [{ name: "edited", passed: true }],
+ judgeScore: 100,
+ judgeSummary: "ok",
+ error: null,
+ tokenUsage: { prompt: 100, completion: 20, total: 120 },
+ },
+ {
+ attempt: 2,
+ passed: false,
+ durationMs: 100,
+ assistantMessageCount: 1,
+ toolCallCount: 0,
+ toolsUsed: [],
+ skillsInvoked: [],
+ checks: [{ name: "edited", passed: false }],
+ judgeScore: 10,
+ judgeSummary: "missed",
+ error: "failed",
+ tokenUsage: { prompt: 10, completion: 5, total: 15 },
+ },
+ ]),
+ ],
+ });
+
+ expect(result.attemptCount).toBe(2);
+ expect(result.passedAttempts).toBe(1);
+ expect(result.passRate).toBe(0.5);
+ expect(result.averageDurationMs).toBe(550);
+ expect(result.averagePassedDurationMs).toBe(1000);
+ expect(result.totalTokenUsage).toEqual({
+ prompt: 110,
+ completion: 25,
+ total: 135,
+ });
+ expect(result.totalPassedTokenUsage).toEqual({
+ prompt: 100,
+ completion: 20,
+ total: 120,
+ });
+ expect(result.averageTokenUsagePerAttempt).toEqual({
+ prompt: 55,
+ completion: 12.5,
+ total: 67.5,
+ });
+ expect(result.averageTokenUsagePerPassedAttempt).toEqual({
+ prompt: 100,
+ completion: 20,
+ total: 120,
+ });
+
+ const summary = formatRunSummary(result);
+ expect(summary).toContain("Average duration (passed): 1000ms");
+ expect(summary).toContain("Average tokens (passed): 120 total");
+ expect(summary).toContain("Average duration (all attempts): 550ms");
+ });
+
+ it("reports passed averages as unavailable when no attempt passes", () => {
+ const result = buildRunResult({
+ mode: "global",
+ runs: 1,
+ runModel: "model-under-test",
+ judgeModel: "judge-model",
+ caseResults: [
+ caseResult([
+ {
+ attempt: 1,
+ passed: false,
+ durationMs: 100,
+ assistantMessageCount: 1,
+ toolCallCount: 0,
+ toolsUsed: [],
+ skillsInvoked: [],
+ checks: [{ name: "edited", passed: false }],
+ judgeScore: 10,
+ judgeSummary: "missed",
+ error: "failed",
+ tokenUsage: { prompt: 10, completion: 5, total: 15 },
+ },
+ ]),
+ ],
+ });
+
+ expect(result.averagePassedDurationMs).toBeNull();
+ expect(result.totalPassedTokenUsage).toBeNull();
+ expect(result.averageTokenUsagePerPassedAttempt).toBeNull();
+ expect(formatRunSummary(result)).toContain(
+ "Average duration (passed): n/a",
+ );
+ });
+
+ it("normalizes passed token averages by passed attempts", () => {
+ const result = buildRunResult({
+ mode: "global",
+ runs: 1,
+ runModel: "model-under-test",
+ judgeModel: "judge-model",
+ caseResults: [
+ caseResult([
+ {
+ attempt: 1,
+ passed: true,
+ durationMs: 1000,
+ assistantMessageCount: 1,
+ toolCallCount: 1,
+ toolsUsed: ["edit_script"],
+ skillsInvoked: [],
+ checks: [{ name: "edited", passed: true }],
+ judgeScore: 100,
+ judgeSummary: "ok",
+ error: null,
+ tokenUsage: { prompt: 100, completion: 20, total: 120 },
+ },
+ {
+ attempt: 2,
+ passed: true,
+ durationMs: 1200,
+ assistantMessageCount: 1,
+ toolCallCount: 1,
+ toolsUsed: ["edit_script"],
+ skillsInvoked: [],
+ checks: [{ name: "edited", passed: true }],
+ judgeScore: 100,
+ judgeSummary: "ok",
+ error: null,
+ tokenUsage: null,
+ },
+ ]),
+ ],
+ });
+
+ expect(result.passedAttempts).toBe(2);
+ expect(result.totalPassedTokenUsage).toEqual({
+ prompt: 100,
+ completion: 20,
+ total: 120,
+ });
+ expect(result.averageTokenUsagePerPassedAttempt).toEqual({
+ prompt: 50,
+ completion: 10,
+ total: 60,
+ });
+ });
+
+ it("records passed-attempt metrics in history", async () => {
+ const tempDir = await mkdtemp(join(tmpdir(), "windmill-ai-evals-"));
+ try {
+ const historyPath = join(tempDir, "history.jsonl");
+ const result = buildRunResult({
+ mode: "global",
+ runs: 1,
+ runModel: "model-under-test",
+ judgeModel: "judge-model",
+ caseResults: [
+ caseResult([
+ {
+ attempt: 1,
+ passed: true,
+ durationMs: 1000,
+ assistantMessageCount: 1,
+ toolCallCount: 1,
+ toolsUsed: ["edit_script"],
+ skillsInvoked: [],
+ checks: [{ name: "edited", passed: true }],
+ judgeScore: 100,
+ judgeSummary: "ok",
+ error: null,
+ tokenUsage: { prompt: 100, completion: 20, total: 120 },
+ },
+ {
+ attempt: 2,
+ passed: false,
+ durationMs: 100,
+ assistantMessageCount: 1,
+ toolCallCount: 0,
+ toolsUsed: [],
+ skillsInvoked: [],
+ checks: [{ name: "edited", passed: false }],
+ judgeScore: 10,
+ judgeSummary: "missed",
+ error: "failed",
+ tokenUsage: { prompt: 10, completion: 5, total: 15 },
+ },
+ ]),
+ ],
+ });
+
+ await appendHistoryRecord(result, historyPath);
+ const record = JSON.parse(await readFile(historyPath, "utf8"));
+
+ expect(record.averageDurationMs).toBe(550);
+ expect(record.averagePassedDurationMs).toBe(1000);
+ expect(record.averageTokenUsagePerAttempt.total).toBe(67.5);
+ expect(record.averageTokenUsagePerPassedAttempt.total).toBe(120);
+ expect(record.cases[0].averageDurationMs).toBe(550);
+ expect(record.cases[0].averagePassedDurationMs).toBe(1000);
+ expect(record.cases[0].averageTokenUsagePerAttempt.total).toBe(67.5);
+ expect(record.cases[0].averageTokenUsagePerPassedAttempt.total).toBe(
+ 120,
+ );
+ } finally {
+ await rm(tempDir, { recursive: true, force: true });
+ }
+ });
+});
diff --git a/ai_evals/core/results.ts b/ai_evals/core/results.ts
index c5f6b8749b..0b84497165 100644
--- a/ai_evals/core/results.ts
+++ b/ai_evals/core/results.ts
@@ -4,12 +4,20 @@ import { execFileSync } from "node:child_process";
import { getAiEvalsRoot, getRepoRoot } from "./cases";
import type {
BenchmarkArtifactFile,
+ BenchmarkAttemptResult,
BenchmarkCaseResult,
BenchmarkRunResult,
BenchmarkTokenUsage,
EvalMode,
} from "./types";
+type AttemptAggregate = {
+ attemptCount: number;
+ durationTotal: number;
+ tokenUsageAttemptCount: number;
+ tokenUsageTotal: BenchmarkTokenUsage | null;
+};
+
export async function writeRunResult(
result: BenchmarkRunResult,
outputPath?: string,
@@ -74,40 +82,15 @@ export function buildRunResult(input: {
mode: EvalMode;
runs: number;
runModel: string | null;
- transport?: BenchmarkRunResult["transport"];
judgeModel: string | null;
caseResults: BenchmarkCaseResult[];
}): BenchmarkRunResult {
- const attemptCount = input.caseResults.reduce(
- (sum, entry) => sum + entry.attempts.length,
- 0,
- );
- const passedAttempts = input.caseResults.reduce(
- (sum, entry) =>
- sum + entry.attempts.filter((attempt) => attempt.passed).length,
- 0,
- );
- const durationTotal = input.caseResults.reduce(
- (sum, entry) =>
- sum +
- entry.attempts.reduce((inner, attempt) => inner + attempt.durationMs, 0),
- 0,
- );
- const tokenUsageTotal = input.caseResults.reduce(
- (sum, entry) => {
- for (const attempt of entry.attempts) {
- if (!attempt.tokenUsage) {
- continue;
- }
- sum ??= { prompt: 0, completion: 0, total: 0 };
- sum.prompt += attempt.tokenUsage.prompt;
- sum.completion += attempt.tokenUsage.completion;
- sum.total += attempt.tokenUsage.total;
- }
- return sum;
- },
- null,
- );
+ const attempts = input.caseResults.flatMap((entry) => entry.attempts);
+ const passedAttemptResults = attempts.filter((attempt) => attempt.passed);
+ const attemptAggregate = aggregateAttempts(attempts);
+ const passedAttemptAggregate = aggregateAttempts(passedAttemptResults);
+ const attemptCount = attemptAggregate.attemptCount;
+ const passedAttempts = passedAttemptAggregate.attemptCount;
return {
version: 1,
@@ -116,22 +99,24 @@ export function buildRunResult(input: {
gitSha: getGitSha(),
runs: input.runs,
runModel: input.runModel,
- transport: input.transport ?? null,
judgeModel: input.judgeModel,
caseCount: input.caseResults.length,
attemptCount,
passedAttempts,
passRate: attemptCount === 0 ? 0 : passedAttempts / attemptCount,
- averageDurationMs: attemptCount === 0 ? 0 : durationTotal / attemptCount,
- totalTokenUsage: tokenUsageTotal,
+ averageDurationMs:
+ attemptCount === 0 ? 0 : attemptAggregate.durationTotal / attemptCount,
+ averagePassedDurationMs: averageDuration(passedAttemptAggregate),
+ totalTokenUsage: attemptAggregate.tokenUsageTotal,
+ totalPassedTokenUsage: passedAttemptAggregate.tokenUsageTotal,
averageTokenUsagePerAttempt:
- attemptCount === 0 || !tokenUsageTotal
+ attemptCount === 0
? null
- : {
- prompt: tokenUsageTotal.prompt / attemptCount,
- completion: tokenUsageTotal.completion / attemptCount,
- total: tokenUsageTotal.total / attemptCount,
- },
+ : averageTokenUsage(attemptAggregate, attemptCount),
+ averageTokenUsagePerPassedAttempt: averageTokenUsage(
+ passedAttemptAggregate,
+ passedAttempts,
+ ),
cases: input.caseResults,
};
}
@@ -140,10 +125,23 @@ export function formatRunSummary(result: BenchmarkRunResult): string {
const lines = [
`${result.mode} benchmark complete`,
`Pass rate: ${formatPercent(result.passRate)} (${result.passedAttempts}/${result.attemptCount})`,
- `Average duration: ${Math.round(result.averageDurationMs)}ms`,
+ `Average duration (passed): ${formatNullableDuration(result.averagePassedDurationMs ?? null)}`,
];
- if (result.transport) {
- lines.splice(1, 0, `Transport: ${result.transport}`);
+
+ if (result.averageTokenUsagePerPassedAttempt) {
+ lines.push(
+ `Average tokens (passed): ${formatTokenUsage(result.averageTokenUsagePerPassedAttempt)}`,
+ );
+ }
+ if (result.passedAttempts < result.attemptCount) {
+ lines.push(
+ `Average duration (all attempts): ${Math.round(result.averageDurationMs)}ms`,
+ );
+ if (result.averageTokenUsagePerAttempt) {
+ lines.push(
+ `Average tokens (all attempts): ${formatTokenUsage(result.averageTokenUsagePerAttempt)}`,
+ );
+ }
}
const failures = collectFailures(result);
@@ -177,6 +175,60 @@ function collectFailures(result: BenchmarkRunResult): string[] {
return failures;
}
+function aggregateAttempts(attempts: BenchmarkAttemptResult[]): AttemptAggregate {
+ const aggregate: AttemptAggregate = {
+ attemptCount: attempts.length,
+ durationTotal: 0,
+ tokenUsageAttemptCount: 0,
+ tokenUsageTotal: null,
+ };
+
+ for (const attempt of attempts) {
+ aggregate.durationTotal += attempt.durationMs;
+ if (!attempt.tokenUsage) {
+ continue;
+ }
+ aggregate.tokenUsageAttemptCount += 1;
+ aggregate.tokenUsageTotal ??= { prompt: 0, completion: 0, total: 0 };
+ aggregate.tokenUsageTotal.prompt += attempt.tokenUsage.prompt;
+ aggregate.tokenUsageTotal.completion += attempt.tokenUsage.completion;
+ aggregate.tokenUsageTotal.total += attempt.tokenUsage.total;
+ }
+
+ return aggregate;
+}
+
+function averageDuration(aggregate: AttemptAggregate): number | null {
+ return aggregate.attemptCount === 0
+ ? null
+ : aggregate.durationTotal / aggregate.attemptCount;
+}
+
+function averageTokenUsage(
+ aggregate: AttemptAggregate,
+ denominator: number,
+): BenchmarkTokenUsage | null {
+ if (denominator === 0 || !aggregate.tokenUsageTotal) {
+ return null;
+ }
+ return {
+ prompt: aggregate.tokenUsageTotal.prompt / denominator,
+ completion: aggregate.tokenUsageTotal.completion / denominator,
+ total: aggregate.tokenUsageTotal.total / denominator,
+ };
+}
+
+function formatNullableDuration(value: number | null): string {
+ return value === null ? "n/a" : `${Math.round(value)}ms`;
+}
+
+function formatTokenUsage(value: BenchmarkTokenUsage): string {
+ const total = Math.round(value.total);
+ const prompt = Math.round(value.prompt);
+ const completion = Math.round(value.completion);
+ return `${total} total (${prompt} prompt, ${completion} completion)`;
+}
+
function defaultFileName(mode: EvalMode): string {
return `${new Date().toISOString().replaceAll(":", "-")}__${mode}.json`;
}
@@ -251,19 +303,21 @@ function toHistoryRecord(result: BenchmarkRunResult) {
mode: result.mode,
runs: result.runs,
runModel: result.runModel,
- transport: result.transport,
judgeModel: result.judgeModel,
caseCount: result.caseCount,
attemptCount: result.attemptCount,
passedAttempts: result.passedAttempts,
passRate: result.passRate,
averageDurationMs: result.averageDurationMs,
+ averagePassedDurationMs: result.averagePassedDurationMs ?? null,
averageJudgeScore:
judgeScores.length === 0
? null
: judgeScores.reduce((sum, score) => sum + score, 0) /
judgeScores.length,
averageTokenUsagePerAttempt: result.averageTokenUsagePerAttempt ?? null,
+ averageTokenUsagePerPassedAttempt:
+ result.averageTokenUsagePerPassedAttempt ?? null,
failedCaseIds: Array.from(
new Set(
result.cases
@@ -274,31 +328,15 @@ function toHistoryRecord(result: BenchmarkRunResult) {
),
),
cases: result.cases.map((caseResult) => {
- const attemptCount = caseResult.attempts.length;
- const passedAttempts = caseResult.attempts.filter(
- (attempt) => attempt.passed,
- ).length;
- const totalDurationMs = caseResult.attempts.reduce(
- (sum, attempt) => sum + attempt.durationMs,
- 0,
+ const attemptAggregate = aggregateAttempts(caseResult.attempts);
+ const passedAttemptAggregate = aggregateAttempts(
+ caseResult.attempts.filter((attempt) => attempt.passed),
);
+ const attemptCount = attemptAggregate.attemptCount;
+ const passedAttempts = passedAttemptAggregate.attemptCount;
const judgeScores = caseResult.attempts.flatMap((attempt) =>
typeof attempt.judgeScore === "number" ? [attempt.judgeScore] : [],
);
- const totalTokenUsage =
- caseResult.attempts.reduce(
- (sum, attempt) => {
- if (!attempt.tokenUsage) {
- return sum;
- }
- sum ??= { prompt: 0, completion: 0, total: 0 };
- sum.prompt += attempt.tokenUsage.prompt;
- sum.completion += attempt.tokenUsage.completion;
- sum.total += attempt.tokenUsage.total;
- return sum;
- },
- null,
- );
return {
id: caseResult.id,
@@ -306,20 +344,23 @@ function toHistoryRecord(result: BenchmarkRunResult) {
passedAttempts,
passRate: attemptCount === 0 ? 0 : passedAttempts / attemptCount,
averageDurationMs:
- attemptCount === 0 ? 0 : totalDurationMs / attemptCount,
+ attemptCount === 0
+ ? 0
+ : attemptAggregate.durationTotal / attemptCount,
+ averagePassedDurationMs: averageDuration(passedAttemptAggregate),
averageJudgeScore:
judgeScores.length === 0
? null
: judgeScores.reduce((sum, score) => sum + score, 0) /
judgeScores.length,
averageTokenUsagePerAttempt:
- attemptCount === 0 || !totalTokenUsage
+ attemptCount === 0
? null
- : {
- prompt: totalTokenUsage.prompt / attemptCount,
- completion: totalTokenUsage.completion / attemptCount,
- total: totalTokenUsage.total / attemptCount,
- },
+ : averageTokenUsage(attemptAggregate, attemptCount),
+ averageTokenUsagePerPassedAttempt: averageTokenUsage(
+ passedAttemptAggregate,
+ passedAttempts,
+ ),
};
}),
};
diff --git a/ai_evals/core/types.ts b/ai_evals/core/types.ts
index 2d612be21a..ecc46591fc 100644
--- a/ai_evals/core/types.ts
+++ b/ai_evals/core/types.ts
@@ -1,7 +1,6 @@
-export const EVAL_MODES = ["cli", "flow", "script", "app"] as const;
+export const EVAL_MODES = ["cli", "flow", "script", "app", "global"] as const;
export type EvalMode = (typeof EVAL_MODES)[number];
-export type FrontendEvalTransport = "direct" | "proxy";
export interface EvalCaseRuntimeBackendPreview {
args?: Record;
@@ -109,6 +108,29 @@ export interface AppValidationSpec {
forbiddenAppContent?: string[];
}
+export interface GlobalDraftRequirement {
+ type: string;
+ path?: string;
+ pathIncludes?: string[];
+ pathStartsWith?: string;
+ triggerKind?: string;
+ language?: string;
+ summaryIncludes?: string[];
+ valueIncludes?: string[];
+ valueExcludes?: string[];
+}
+
+export interface GlobalValidationSpec {
+ draftCountAtLeast?: number;
+ draftCountExactly?: number;
+ requiredDrafts?: GlobalDraftRequirement[];
+ forbiddenDrafts?: Array<{
+ type: string;
+ path: string;
+ triggerKind?: string;
+ }>;
+}
+
export interface CliValidationSpec {
requiredSkills?: string[];
forbiddenSkills?: string[];
@@ -137,10 +159,11 @@ export interface ToolCallArgumentRule {
export interface ToolValidationSpec {
requiredToolsUsed?: string[];
+ forbiddenToolsUsed?: string[];
toolCallArgs?: ToolCallArgumentRule[];
}
-export type EvalValidationSpec = FlowValidationSpec | AppValidationSpec;
+export type EvalValidationSpec = FlowValidationSpec | AppValidationSpec | GlobalValidationSpec;
export interface EvalCase {
id: string;
@@ -297,15 +320,17 @@ export interface BenchmarkRunResult {
gitSha: string | null;
runs: number;
runModel: string | null;
- transport: FrontendEvalTransport | null;
judgeModel: string | null;
caseCount: number;
attemptCount: number;
passedAttempts: number;
passRate: number;
averageDurationMs: number;
+ averagePassedDurationMs?: number | null;
totalTokenUsage?: BenchmarkTokenUsage | null;
+ totalPassedTokenUsage?: BenchmarkTokenUsage | null;
averageTokenUsagePerAttempt?: BenchmarkTokenUsage | null;
+ averageTokenUsagePerPassedAttempt?: BenchmarkTokenUsage | null;
artifactsPath?: string | null;
cases: BenchmarkCaseResult[];
}
diff --git a/ai_evals/core/validators.test.ts b/ai_evals/core/validators.test.ts
index 406172b955..6010f6351a 100644
--- a/ai_evals/core/validators.test.ts
+++ b/ai_evals/core/validators.test.ts
@@ -2,6 +2,7 @@ import { describe, expect, it } from "bun:test";
import {
validateAppState,
validateCliWorkspace,
+ validateGlobalState,
validateScriptState,
validateToolExpectations,
} from "./validators";
@@ -117,6 +118,293 @@ describe("validateToolExpectations", () => {
details: 'rejected prefixes: schedules/; values: "schedules/greet_user_daily"',
});
});
+
+ it("rejects forbidden tool usage", () => {
+ const checks = validateToolExpectations({
+ run: {
+ success: true,
+ actual: {},
+ assistantMessageCount: 1,
+ toolCallCount: 1,
+ toolsUsed: ["write_script", "deploy_workspace_item"],
+ skillsInvoked: [],
+ },
+ toolExpect: {
+ forbiddenToolsUsed: ["deploy_workspace_item"],
+ },
+ });
+
+ expect(checks).toContainEqual({
+ name: "does not use deploy_workspace_item",
+ passed: false,
+ details: "tools used: write_script, deploy_workspace_item",
+ });
+ });
+});
+
+describe("validateGlobalState", () => {
+ it("accepts a required script draft", () => {
+ const checks = validateGlobalState({
+ actual: {
+ drafts: [
+ {
+ type: "script",
+ path: "f/evals/global/greet_user",
+ language: "bun",
+ value:
+ "export async function main(name: string) {\n return `Hello, ${name}!`\n}\n",
+ isDraft: true,
+ },
+ ],
+ },
+ validate: {
+ draftCountExactly: 1,
+ requiredDrafts: [
+ {
+ type: "script",
+ path: "f/evals/global/greet_user",
+ language: "bun",
+ valueIncludes: ["Hello"],
+ },
+ ],
+ },
+ });
+
+ expect(checks.every((check) => check.passed)).toBe(true);
+ });
+
+ it("fails when a required draft is missing", () => {
+ const checks = validateGlobalState({
+ actual: {
+ drafts: [],
+ },
+ validate: {
+ requiredDrafts: [
+ {
+ type: "script",
+ path: "f/evals/global/greet_user",
+ },
+ ],
+ },
+ });
+
+ expect(checks).toContainEqual({
+ name: "global includes script draft f/evals/global/greet_user",
+ passed: false,
+ details: "drafts: none",
+ });
+ });
+
+ it("accepts a required script draft without an exact path", () => {
+ const checks = validateGlobalState({
+ actual: {
+ drafts: [
+ {
+ type: "script",
+ path: "f/team_tools/friendly_greeting",
+ language: "bun",
+ summary: "Friendly greeting helper",
+ value:
+ "export async function main(name: string) {\n return `Hello, ${name}!`\n}\n",
+ isDraft: true,
+ },
+ ],
+ },
+ validate: {
+ draftCountExactly: 1,
+ requiredDrafts: [
+ {
+ type: "script",
+ pathIncludes: ["greeting"],
+ language: "bun",
+ summaryIncludes: ["Friendly"],
+ valueIncludes: ["Hello"],
+ },
+ ],
+ },
+ });
+
+ expect(checks.every((check) => check.passed)).toBe(true);
+ });
+
+ it("reports flexible global draft path filters when no draft matches", () => {
+ const checks = validateGlobalState({
+ actual: {
+ drafts: [
+ {
+ type: "script",
+ path: "f/team_tools/friendly_greeting",
+ language: "bun",
+ value:
+ "export async function main(name: string) {\n return `Hello, ${name}!`\n}\n",
+ isDraft: true,
+ },
+ ],
+ },
+ validate: {
+ requiredDrafts: [
+ {
+ type: "script",
+ pathIncludes: ["invoice"],
+ },
+ ],
+ },
+ });
+
+ expect(checks).toContainEqual({
+ name: "global includes script draft (path includes invoice)",
+ passed: false,
+ details: "drafts: script:f/team_tools/friendly_greeting",
+ });
+ });
+
+ it("does not require a TypeScript entrypoint for non-TypeScript script drafts", () => {
+ const checks = validateGlobalState({
+ actual: {
+ drafts: [
+ {
+ type: "script",
+ path: "f/evals/global/greet_python",
+ language: "python3",
+ value: "def main(name: str):\n return f'Hello, {name}!'\n",
+ isDraft: true,
+ },
+ ],
+ },
+ });
+
+ expect(checks.some((check) => check.name.includes("exports entrypoint"))).toBe(
+ false
+ );
+ expect(checks.every((check) => check.passed)).toBe(true);
+ });
+
+ it("allows read-only global cases without draft expectations", () => {
+ const checks = validateGlobalState({
+ actual: {
+ drafts: [],
+ },
+ });
+
+ expect(
+ checks.some(
+ (check) => check.name === "global produced at least one draft"
+ )
+ ).toBe(false);
+ expect(checks.every((check) => check.passed)).toBe(true);
+ });
+
+ it("matches expected global draft fixtures", () => {
+ const checks = validateGlobalState({
+ actual: {
+ drafts: [
+ {
+ type: "script",
+ path: "f/evals/global/greet_user",
+ language: "bun",
+ value:
+ "export async function main(name: string) {\r\n return `Hello, ${name}!`\r\n}\r\n",
+ isDraft: true,
+ },
+ ],
+ },
+ expected: {
+ drafts: [
+ {
+ type: "script",
+ path: "f/evals/global/greet_user",
+ language: "bun",
+ value:
+ "export async function main(name: string) {\n return `Hello, ${name}!`\n}\n",
+ isDraft: true,
+ },
+ ],
+ },
+ });
+
+ expect(checks).toContainEqual({
+ name: "global drafts match expected",
+ passed: true,
+ });
+ });
+
+ it("fails when expected global draft fixtures differ", () => {
+ const checks = validateGlobalState({
+ actual: {
+ drafts: [
+ {
+ type: "script",
+ path: "f/evals/global/greet_user",
+ language: "bun",
+ value:
+ "export async function main(name: string) {\n return `Hello, ${name}!`\n}\n",
+ isDraft: true,
+ },
+ ],
+ },
+ expected: {
+ drafts: [
+ {
+ type: "script",
+ path: "f/evals/global/greet_user",
+ language: "bun",
+ value:
+ "export async function main(name: string) {\n return `Bonjour, ${name}!`\n}\n",
+ isDraft: true,
+ },
+ ],
+ },
+ });
+
+ const expectedMatchCheck = checks.find(
+ (check) => check.name === "global drafts match expected"
+ );
+ expect(expectedMatchCheck?.passed).toBe(false);
+ expect(expectedMatchCheck?.details).toContain(
+ "script:f/evals/global/greet_user value differs"
+ );
+ expect(expectedMatchCheck?.details).toContain("Hello");
+ expect(expectedMatchCheck?.details).toContain("Bonjour");
+ });
+
+ it("explains expected global draft metadata mismatches", () => {
+ const checks = validateGlobalState({
+ actual: {
+ drafts: [
+ {
+ type: "script",
+ path: "f/evals/global/greet_user",
+ language: "bun",
+ value:
+ "export async function main(name: string) {\n return `Hello, ${name}!`\n}\n",
+ isDraft: true,
+ },
+ ],
+ },
+ expected: {
+ drafts: [
+ {
+ type: "script",
+ path: "f/evals/global/greet_user",
+ language: "python3",
+ value:
+ "export async function main(name: string) {\n return `Hello, ${name}!`\n}\n",
+ isDraft: true,
+ },
+ ],
+ },
+ });
+
+ const expectedMatchCheck = checks.find(
+ (check) => check.name === "global drafts match expected"
+ );
+ expect(expectedMatchCheck?.passed).toBe(false);
+ expect(expectedMatchCheck?.details).toContain(
+ "script:f/evals/global/greet_user language differs"
+ );
+ expect(expectedMatchCheck?.details).toContain('actual="bun"');
+ expect(expectedMatchCheck?.details).toContain('expected="python3"');
+ });
});
describe("validateAppState", () => {
diff --git a/ai_evals/core/validators.ts b/ai_evals/core/validators.ts
index e690b3d7eb..693d34a013 100644
--- a/ai_evals/core/validators.ts
+++ b/ai_evals/core/validators.ts
@@ -6,6 +6,7 @@ import type {
CliTrace,
CliValidationSpec,
FlowValidationSpec,
+ GlobalValidationSpec,
ModeRunOutput,
ToolValidationSpec,
} from "./types";
@@ -51,6 +52,20 @@ export interface AppDatatableState {
error?: string;
}
+export interface GlobalDraftState {
+ drafts: GlobalDraft[];
+}
+
+export interface GlobalDraft {
+ type: string;
+ path: string;
+ triggerKind?: string;
+ summary?: string;
+ language?: string;
+ value?: unknown;
+ isDraft?: boolean;
+}
+
const TS_LIKE_LANGUAGES = new Set(["bun", "deno", "nativets", "bunnative", "ts", "typescript"]);
const CONTROL_FLOW_MODULE_TYPES = new Set(["branchone", "branchall", "forloopflow", "whileloopflow"]);
@@ -154,6 +169,16 @@ export function validateToolExpectations(input: {
);
}
+ for (const toolName of expect.forbiddenToolsUsed ?? []) {
+ checks.push(
+ check(
+ `does not use ${toolName}`,
+ !input.run.toolsUsed.includes(toolName),
+ `tools used: ${input.run.toolsUsed.join(", ") || "none"}`
+ )
+ );
+ }
+
for (const rule of expect.toolCallArgs ?? []) {
const calls = toolCallDetails.filter((call) => call.name === rule.tool);
checks.push(
@@ -202,6 +227,162 @@ export function validateToolExpectations(input: {
return checks;
}
+export function validateGlobalState(input: {
+ actual: GlobalDraftState;
+ expected?: GlobalDraftState;
+ validate?: GlobalValidationSpec;
+}): BenchmarkCheck[] {
+ const drafts = input.actual.drafts ?? [];
+ const checks: BenchmarkCheck[] = [];
+
+ // Read-only global cases are valid; only enforce draft production when the
+ // case explicitly asks for draft output.
+ if (globalValidationExpectsDrafts(input)) {
+ checks.push(
+ check(
+ "global produced at least one draft",
+ drafts.length > 0,
+ `drafts=${drafts.length}`
+ )
+ );
+ }
+
+ checks.push(
+ check(
+ "all global outputs are drafts",
+ drafts.every((draft) => draft.isDraft === true),
+ summarizeGlobalDrafts(drafts)
+ )
+ );
+
+ for (const draft of drafts) {
+ if (draft.type !== "script" || typeof draft.value !== "string") {
+ continue;
+ }
+
+ const language = (draft.language ?? "bun").toLowerCase();
+ const syntaxErrors = getScriptSyntaxErrors(draft.value, language);
+ if (TS_LIKE_LANGUAGES.has(language)) {
+ checks.push(
+ check(
+ `script draft ${draft.path} exports entrypoint`,
+ hasSupportedEntrypoint(draft.value)
+ )
+ );
+ }
+ checks.push(
+ check(
+ `script draft ${draft.path} has no syntax errors`,
+ syntaxErrors.length === 0,
+ summarizeProblems(syntaxErrors)
+ )
+ );
+ }
+
+ if (input.expected) {
+ checks.push(
+ check(
+ "global drafts match expected",
+ globalDraftStatesEqual(input.actual, input.expected),
+ describeGlobalDraftStateMismatch(input.actual, input.expected)
+ )
+ );
+ }
+
+ const validate = input.validate;
+ if (!validate) {
+ return checks;
+ }
+
+ if (validate.draftCountAtLeast !== undefined) {
+ checks.push(
+ check(
+ `global includes at least ${validate.draftCountAtLeast} draft(s)`,
+ drafts.length >= validate.draftCountAtLeast,
+ `drafts=${drafts.length}`
+ )
+ );
+ }
+
+ if (validate.draftCountExactly !== undefined) {
+ checks.push(
+ check(
+ `global includes exactly ${validate.draftCountExactly} draft(s)`,
+ drafts.length === validate.draftCountExactly,
+ `drafts=${drafts.length}`
+ )
+ );
+ }
+
+ for (const required of validate.requiredDrafts ?? []) {
+ const requirementLabel = formatGlobalDraftRequirement(required);
+ const draft = findGlobalDraft(drafts, required);
+ checks.push(
+ check(
+ `global includes ${requirementLabel}`,
+ Boolean(draft),
+ summarizeGlobalDrafts(drafts)
+ )
+ );
+ if (!draft) {
+ continue;
+ }
+
+ if (required.language !== undefined) {
+ checks.push(
+ check(
+ `${requirementLabel} uses ${required.language}`,
+ draft.language === required.language,
+ `language=${draft.language ?? "(none)"}`
+ )
+ );
+ }
+
+ for (const snippet of required.summaryIncludes ?? []) {
+ checks.push(
+ check(
+ `${requirementLabel} summary includes '${snippet}'`,
+ normalizeText(draft.summary ?? "").includes(normalizeText(snippet)),
+ `summary=${draft.summary ?? ""}`
+ )
+ );
+ }
+
+ const valueText = stringifyGlobalDraftValue(draft.value);
+ for (const snippet of required.valueIncludes ?? []) {
+ checks.push(
+ check(
+ `${requirementLabel} value includes '${snippet}'`,
+ normalizeText(valueText).includes(normalizeText(snippet)),
+ truncateForDetails(valueText)
+ )
+ );
+ }
+
+ for (const snippet of required.valueExcludes ?? []) {
+ checks.push(
+ check(
+ `${requirementLabel} value excludes '${snippet}'`,
+ !normalizeText(valueText).includes(normalizeText(snippet)),
+ truncateForDetails(valueText)
+ )
+ );
+ }
+ }
+
+ for (const forbidden of validate.forbiddenDrafts ?? []) {
+ checks.push(
+ check(
+ `global does not include ${forbidden.type} draft ${forbidden.path}`,
+ !findGlobalDraft(drafts, forbidden),
+ summarizeGlobalDrafts(drafts)
+ )
+ );
+ }
+
+ return checks;
+}
+
export function validateAppState(input: {
actual: AppFilesState;
initial?: AppFilesState;
@@ -433,6 +614,286 @@ function summarizeProblems(problems: string[], limit = 5): string | undefined {
return `${problems.slice(0, limit).join("; ")}; ...and ${problems.length - limit} more`;
}
+function findGlobalDraft(
+ drafts: GlobalDraft[],
+ requirement: {
+ type: string;
+ path?: string;
+ pathIncludes?: string[];
+ pathStartsWith?: string;
+ triggerKind?: string;
+ summaryIncludes?: string[];
+ valueIncludes?: string[];
+ valueExcludes?: string[];
+ }
+): GlobalDraft | undefined {
+ const candidates = drafts.filter((draft) =>
+ globalDraftMatchesLocator(draft, requirement)
+ );
+ return (
+ candidates.find((draft) => globalDraftMatchesContent(draft, requirement)) ??
+ candidates[0]
+ );
+}
+
+function globalDraftMatchesLocator(
+ draft: GlobalDraft,
+ requirement: {
+ type: string;
+ path?: string;
+ pathIncludes?: string[];
+ pathStartsWith?: string;
+ triggerKind?: string;
+ }
+): boolean {
+ return (
+ draft.type === requirement.type &&
+ (requirement.path === undefined || draft.path === requirement.path) &&
+ (requirement.pathStartsWith === undefined ||
+ draft.path.startsWith(requirement.pathStartsWith)) &&
+ (requirement.pathIncludes ?? []).every((snippet) =>
+ normalizeText(draft.path).includes(normalizeText(snippet))
+ ) &&
+ (requirement.triggerKind === undefined ||
+ draft.triggerKind === requirement.triggerKind)
+ );
+}
+
+function globalDraftMatchesContent(
+ draft: GlobalDraft,
+ requirement: {
+ summaryIncludes?: string[];
+ valueIncludes?: string[];
+ valueExcludes?: string[];
+ }
+): boolean {
+ const summary = normalizeText(draft.summary ?? "");
+ const value = normalizeText(stringifyGlobalDraftValue(draft.value));
+ return (
+ (requirement.summaryIncludes ?? []).every((snippet) =>
+ summary.includes(normalizeText(snippet))
+ ) &&
+ (requirement.valueIncludes ?? []).every((snippet) =>
+ value.includes(normalizeText(snippet))
+ ) &&
+ (requirement.valueExcludes ?? []).every(
+ (snippet) => !value.includes(normalizeText(snippet))
+ )
+ );
+}
+
+function formatGlobalDraftRequirement(
+ requirement: {
+ type: string;
+ path?: string;
+ pathIncludes?: string[];
+ pathStartsWith?: string;
+ triggerKind?: string;
+ }
+): string {
+ const typeLabel =
+ requirement.triggerKind === undefined
+ ? requirement.type
+ : `${requirement.triggerKind} ${requirement.type}`;
+ if (requirement.path !== undefined) {
+ return `${typeLabel} draft ${requirement.path}`;
+ }
+
+ const filters = [
+ ...(requirement.pathStartsWith === undefined
+ ? []
+ : [`path starts with ${requirement.pathStartsWith}`]),
+ ...(requirement.pathIncludes ?? []).map(
+ (snippet) => `path includes ${snippet}`
+ ),
+ ];
+ return filters.length === 0
+ ? `${typeLabel} draft`
+ : `${typeLabel} draft (${filters.join(", ")})`;
+}
+
+function summarizeGlobalDrafts(drafts: GlobalDraft[]): string {
+ const summary = drafts
+ .map((draft) => formatGlobalDraftKey(draft))
+ .join(", ");
+ return `drafts: ${summary || "none"}`;
+}
+
+function formatGlobalDraftKey(draft: GlobalDraft): string {
+ return `${draft.type}${draft.triggerKind ? `:${draft.triggerKind}` : ""}:${draft.path}`;
+}
+
+function globalValidationExpectsDrafts(input: {
+ expected?: GlobalDraftState;
+ validate?: GlobalValidationSpec;
+}): boolean {
+ const validate = input.validate;
+ return (
+ (input.expected?.drafts?.length ?? 0) > 0 ||
+ (validate?.requiredDrafts?.length ?? 0) > 0 ||
+ (validate?.draftCountAtLeast ?? 0) > 0 ||
+ (validate?.draftCountExactly ?? 0) > 0
+ );
+}
+
+function globalDraftStatesEqual(left: GlobalDraftState, right: GlobalDraftState): boolean {
+ return (
+ JSON.stringify(canonicalizeGlobalDrafts(left.drafts ?? [])) ===
+ JSON.stringify(canonicalizeGlobalDrafts(right.drafts ?? []))
+ );
+}
+
+function describeGlobalDraftStateMismatch(
+ actual: GlobalDraftState,
+ expected: GlobalDraftState
+): string {
+ const actualDrafts = actual.drafts ?? [];
+ const expectedDrafts = expected.drafts ?? [];
+ const actualByKey = new Map(
+ actualDrafts.map((draft) => [globalDraftSortKey(draft), draft] as const)
+ );
+ const expectedByKey = new Map(
+ expectedDrafts.map((draft) => [globalDraftSortKey(draft), draft] as const)
+ );
+
+ for (const key of Array.from(expectedByKey.keys()).sort()) {
+ const expectedDraft = expectedByKey.get(key);
+ if (expectedDraft && !actualByKey.has(key)) {
+ return `missing expected draft ${formatGlobalDraftKey(expectedDraft)}; actual=${summarizeGlobalDrafts(actualDrafts)}`;
+ }
+ }
+
+ for (const key of Array.from(actualByKey.keys()).sort()) {
+ const actualDraft = actualByKey.get(key);
+ if (actualDraft && !expectedByKey.has(key)) {
+ return `unexpected draft ${formatGlobalDraftKey(actualDraft)}; expected=${summarizeGlobalDrafts(expectedDrafts)}`;
+ }
+ }
+
+ for (const key of Array.from(expectedByKey.keys()).sort()) {
+ const actualDraft = actualByKey.get(key);
+ const expectedDraft = expectedByKey.get(key);
+ if (!actualDraft || !expectedDraft) {
+ continue;
+ }
+
+ const fieldMismatch = describeGlobalDraftFieldMismatch(
+ formatGlobalDraftKey(expectedDraft),
+ actualDraft,
+ expectedDraft
+ );
+ if (fieldMismatch) {
+ return fieldMismatch;
+ }
+ }
+
+ return `actual=${summarizeGlobalDrafts(actualDrafts)}; expected=${summarizeGlobalDrafts(expectedDrafts)}`;
+}
+
+function describeGlobalDraftFieldMismatch(
+ key: string,
+ actual: GlobalDraft,
+ expected: GlobalDraft
+): string | undefined {
+ const fields: Array<"language" | "summary" | "value" | "isDraft"> = [
+ "language",
+ "summary",
+ "value",
+ "isDraft",
+ ];
+
+ for (const field of fields) {
+ const actualValue = comparableGlobalDraftFieldValue(actual, field);
+ const expectedValue = comparableGlobalDraftFieldValue(expected, field);
+ if (JSON.stringify(actualValue) === JSON.stringify(expectedValue)) {
+ continue;
+ }
+
+ return `${key} ${field} differs: actual=${formatGlobalDraftFieldValue(
+ actualValue
+ )}; expected=${formatGlobalDraftFieldValue(expectedValue)}`;
+ }
+
+ return undefined;
+}
+
+function comparableGlobalDraftFieldValue(
+ draft: GlobalDraft,
+ field: "language" | "summary" | "value" | "isDraft"
+): unknown {
+ if (field === "summary" && typeof draft.summary === "string") {
+ return normalizeText(draft.summary);
+ }
+ if (field === "value" && typeof draft.value === "string") {
+ return normalizeText(draft.value);
+ }
+ if (field === "value") {
+ return canonicalizeJsonValue(draft.value);
+ }
+ return draft[field];
+}
+
+function formatGlobalDraftFieldValue(value: unknown): string {
+ if (value === undefined) {
+ return "(missing)";
+ }
+ return truncateForDetails(JSON.stringify(value), 300);
+}
+
+function canonicalizeGlobalDrafts(drafts: GlobalDraft[]): unknown[] {
+ return drafts
+ .slice()
+ .sort((left, right) => globalDraftSortKey(left).localeCompare(globalDraftSortKey(right)))
+ .map((draft) =>
+ canonicalizeJsonValue({
+ type: draft.type,
+ path: draft.path,
+ triggerKind: draft.triggerKind,
+ language: draft.language,
+ summary:
+ typeof draft.summary === "string" ? normalizeText(draft.summary) : draft.summary,
+ value:
+ typeof draft.value === "string"
+ ? normalizeText(draft.value)
+ : canonicalizeJsonValue(draft.value),
+ isDraft: draft.isDraft,
+ })
+ );
+}
+
+function globalDraftSortKey(draft: GlobalDraft): string {
+ return `${draft.type}:${draft.triggerKind ?? ""}:${draft.path}`;
+}
+
+function canonicalizeJsonValue(value: unknown): unknown {
+ if (Array.isArray(value)) {
+ return value.map(canonicalizeJsonValue);
+ }
+ if (value && typeof value === "object") {
+ return Object.fromEntries(
+ Object.entries(value as Record)
+ .sort(([left], [right]) => left.localeCompare(right))
+ .map(([key, nested]) => [key, canonicalizeJsonValue(nested)])
+ );
+ }
+ return value;
+}
+
+function stringifyGlobalDraftValue(value: unknown): string {
+ if (typeof value === "string") {
+ return value;
+ }
+ return JSON.stringify(value ?? null, null, 2);
+}
+
+function truncateForDetails(value: string, maxLength = 500): string {
+ const normalized = value.replace(/\s+/g, " ").trim();
+ if (normalized.length <= maxLength) {
+ return normalized;
+ }
+ return `${normalized.slice(0, Math.max(0, maxLength - 3))}...`;
+}
+
function validateCliExpectations(
assistantOutput: string,
trace: CliTrace | undefined,
diff --git a/ai_evals/core/windmillBackendSettings.test.ts b/ai_evals/core/windmillBackendSettings.test.ts
new file mode 100644
index 0000000000..200bc4aa7a
--- /dev/null
+++ b/ai_evals/core/windmillBackendSettings.test.ts
@@ -0,0 +1,63 @@
+import { afterEach, describe, expect, it } from "bun:test";
+import { resolveWindmillBackendSettings } from "./windmillBackendSettings";
+
+const ENV_KEYS = [
+ "WMILL_AI_EVAL_BACKEND_URL",
+ "WINDMILL_URL",
+ "WINDMILL_BASE_URL",
+ "REMOTE",
+ "WMILL_AI_EVAL_BACKEND_EMAIL",
+ "WMILL_AI_EVAL_BACKEND_PASSWORD",
+ "WMILL_AI_EVAL_BACKEND_WORKSPACE",
+] as const;
+
+const ORIGINAL_ENV = Object.fromEntries(
+ ENV_KEYS.map((key) => [key, process.env[key]]),
+) as Record<(typeof ENV_KEYS)[number], string | undefined>;
+
+afterEach(() => {
+ for (const key of ENV_KEYS) {
+ const value = ORIGINAL_ENV[key];
+ if (value === undefined) {
+ delete process.env[key];
+ } else {
+ process.env[key] = value;
+ }
+ }
+});
+
+describe("resolveWindmillBackendSettings", () => {
+ it("uses backend URL/auth defaults and the optional explicit workspace", () => {
+ delete process.env.WMILL_AI_EVAL_BACKEND_URL;
+ delete process.env.WINDMILL_URL;
+ delete process.env.WINDMILL_BASE_URL;
+ delete process.env.REMOTE;
+ process.env.WMILL_AI_EVAL_BACKEND_WORKSPACE = "shared-evals";
+
+ expect(resolveWindmillBackendSettings()).toEqual({
+ baseUrl: "http://127.0.0.1:8000",
+ email: "admin@windmill.dev",
+ password: "changeme",
+ workspaceOverride: "shared-evals",
+ });
+ });
+
+ it("does not expose workspace retention knobs", () => {
+ process.env.WMILL_AI_EVAL_BACKEND_URL = "http://backend.test/";
+
+ const settings = resolveWindmillBackendSettings();
+
+ expect(settings).toEqual({
+ baseUrl: "http://backend.test",
+ email: "admin@windmill.dev",
+ password: "changeme",
+ workspaceOverride: undefined,
+ });
+ expect(Object.keys(settings).sort()).toEqual([
+ "baseUrl",
+ "email",
+ "password",
+ "workspaceOverride",
+ ]);
+ });
+});
diff --git a/ai_evals/core/windmillBackendSettings.ts b/ai_evals/core/windmillBackendSettings.ts
index c3a0a52d47..2388c32b4d 100644
--- a/ai_evals/core/windmillBackendSettings.ts
+++ b/ai_evals/core/windmillBackendSettings.ts
@@ -2,9 +2,7 @@ export interface WindmillBackendSettings {
baseUrl: string;
email: string;
password: string;
- keepWorkspaces: boolean;
workspaceOverride?: string;
- workspacePrefix: string;
}
export function resolveWindmillBackendSettings(): WindmillBackendSettings {
@@ -18,13 +16,9 @@ export function resolveWindmillBackendSettings(): WindmillBackendSettings {
),
email: process.env.WMILL_AI_EVAL_BACKEND_EMAIL ?? "admin@windmill.dev",
password: process.env.WMILL_AI_EVAL_BACKEND_PASSWORD ?? "changeme",
- keepWorkspaces: isTruthy(process.env.WMILL_AI_EVAL_KEEP_WORKSPACES),
workspaceOverride: sanitizeOptionalWorkspaceId(
process.env.WMILL_AI_EVAL_BACKEND_WORKSPACE,
),
- workspacePrefix: sanitizeWorkspacePrefix(
- process.env.WMILL_AI_EVAL_WORKSPACE_PREFIX ?? "ai-evals",
- ),
};
}
@@ -43,25 +37,9 @@ function normalizeBaseUrl(value: string): string {
return value.replace(/\/+$/, "");
}
-function sanitizeWorkspacePrefix(value: string): string {
- const sanitized = value
- .trim()
- .toLowerCase()
- .replace(/[^a-z0-9-]+/g, "-")
- .replace(/^-+|-+$/g, "");
- return sanitized.length > 0 ? sanitized : "ai-evals";
-}
-
function sanitizeOptionalWorkspaceId(
value: string | undefined,
): string | undefined {
const trimmed = value?.trim();
return trimmed ? trimmed : undefined;
}
-
-function isTruthy(value: string | undefined): boolean {
- if (!value) {
- return false;
- }
- return ["1", "true", "yes", "on"].includes(value.trim().toLowerCase());
-}
diff --git a/ai_evals/fixtures/frontend/global/initial/current_greeting_live_script.json b/ai_evals/fixtures/frontend/global/initial/current_greeting_live_script.json
new file mode 100644
index 0000000000..98538a1ccb
--- /dev/null
+++ b/ai_evals/fixtures/frontend/global/initial/current_greeting_live_script.json
@@ -0,0 +1,66 @@
+{
+ "workspace": {
+ "scripts": [
+ {
+ "path": "f/evals/global/format_greeting",
+ "summary": "Format a deployed greeting",
+ "description": "Returns a plain greeting for a provided name.",
+ "language": "bun",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ }
+ },
+ "required": ["name"]
+ },
+ "content": "export async function main(name: string) {\n return `Hello, ${name}`\n}\n"
+ },
+ {
+ "path": "f/evals/global/format_greeting_archive",
+ "summary": "Archived greeting formatter",
+ "description": "Older greeting formatter kept for reference.",
+ "language": "bun",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ }
+ },
+ "required": ["name"]
+ },
+ "content": "export async function main(name: string) {\n return `Hi, ${name}`\n}\n"
+ }
+ ]
+ },
+ "liveEditorDrafts": [
+ {
+ "type": "script",
+ "storagePath": "f/evals/global/current_greeting",
+ "effectivePath": "f/evals/global/current_greeting",
+ "value": {
+ "path": "f/evals/global/current_greeting",
+ "summary": "Open greeting formatter",
+ "description": "Formats a greeting in the live editor.",
+ "language": "bun",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ }
+ },
+ "required": ["name"]
+ },
+ "content": "export async function main(name: string) {\n return `Hello, ${name}`\n}\n",
+ "is_template": false,
+ "kind": "script"
+ }
+ }
+ ]
+}
diff --git a/ai_evals/fixtures/frontend/global/initial/current_invoice_live_flow.json b/ai_evals/fixtures/frontend/global/initial/current_invoice_live_flow.json
new file mode 100644
index 0000000000..5c2ffcb0f0
--- /dev/null
+++ b/ai_evals/fixtures/frontend/global/initial/current_invoice_live_flow.json
@@ -0,0 +1,118 @@
+{
+ "workspace": {
+ "flows": [
+ {
+ "path": "f/evals/global/process_invoice",
+ "summary": "Deployed invoice processor",
+ "description": "Calculates invoice totals from a subtotal.",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "type": "object",
+ "properties": {
+ "subtotal": {
+ "type": "number"
+ }
+ },
+ "required": ["subtotal"]
+ },
+ "value": {
+ "modules": [
+ {
+ "id": "calculate_total",
+ "summary": "Calculate total from subtotal",
+ "value": {
+ "type": "rawscript",
+ "language": "bun",
+ "content": "export async function main(subtotal: number) {\n return { subtotal, total: subtotal }\n}\n",
+ "input_transforms": {
+ "subtotal": {
+ "type": "javascript",
+ "expr": "flow_input.subtotal"
+ }
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "path": "f/evals/global/process_refund",
+ "summary": "Refund processor",
+ "description": "Calculates refund totals.",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "type": "object",
+ "properties": {
+ "subtotal": {
+ "type": "number"
+ }
+ },
+ "required": ["subtotal"]
+ },
+ "value": {
+ "modules": [
+ {
+ "id": "calculate_total",
+ "summary": "Calculate refund total",
+ "value": {
+ "type": "rawscript",
+ "language": "bun",
+ "content": "export async function main(subtotal: number) {\n return { subtotal, total: subtotal }\n}\n",
+ "input_transforms": {
+ "subtotal": {
+ "type": "javascript",
+ "expr": "flow_input.subtotal"
+ }
+ }
+ }
+ }
+ ]
+ }
+ }
+ ]
+ },
+ "liveEditorDrafts": [
+ {
+ "type": "flow",
+ "storagePath": "f/evals/global/current_invoice_flow",
+ "effectivePath": "f/evals/global/current_invoice_flow",
+ "value": {
+ "path": "f/evals/global/current_invoice_flow",
+ "summary": "Open invoice processor",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "type": "object",
+ "properties": {
+ "subtotal": {
+ "type": "number"
+ }
+ },
+ "required": ["subtotal"]
+ },
+ "value": {
+ "modules": [
+ {
+ "id": "calculate_total",
+ "summary": "Calculate total from subtotal",
+ "value": {
+ "type": "rawscript",
+ "language": "bun",
+ "content": "export async function main(subtotal: number) {\n return { subtotal, total: subtotal }\n}\n",
+ "input_transforms": {
+ "subtotal": {
+ "type": "javascript",
+ "expr": "flow_input.subtotal"
+ }
+ }
+ }
+ }
+ ]
+ },
+ "edited_by": "",
+ "edited_at": "",
+ "archived": false,
+ "extra_perms": {}
+ }
+ }
+ ]
+}
diff --git a/ai_evals/fixtures/frontend/global/initial/format_greeting_script.json b/ai_evals/fixtures/frontend/global/initial/format_greeting_script.json
new file mode 100644
index 0000000000..e66eee2ed2
--- /dev/null
+++ b/ai_evals/fixtures/frontend/global/initial/format_greeting_script.json
@@ -0,0 +1,23 @@
+{
+ "workspace": {
+ "scripts": [
+ {
+ "path": "f/evals/global/format_greeting",
+ "summary": "Format a greeting for a provided name",
+ "description": "Returns a plain greeting for the provided name.",
+ "language": "bun",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ }
+ },
+ "required": ["name"]
+ },
+ "content": "export async function main(name: string) {\n return `Hello, ${name}`\n}\n"
+ }
+ ]
+ }
+}
diff --git a/ai_evals/fixtures/frontend/global/initial/process_invoice_flow.json b/ai_evals/fixtures/frontend/global/initial/process_invoice_flow.json
new file mode 100644
index 0000000000..b9b4c675c5
--- /dev/null
+++ b/ai_evals/fixtures/frontend/global/initial/process_invoice_flow.json
@@ -0,0 +1,40 @@
+{
+ "workspace": {
+ "flows": [
+ {
+ "path": "f/evals/global/process_invoice",
+ "summary": "Process an invoice subtotal",
+ "description": "Calculates invoice totals from a subtotal.",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "type": "object",
+ "properties": {
+ "subtotal": {
+ "type": "number"
+ }
+ },
+ "required": ["subtotal"]
+ },
+ "value": {
+ "modules": [
+ {
+ "id": "calculate_total",
+ "summary": "Calculate total from subtotal",
+ "value": {
+ "type": "rawscript",
+ "language": "bun",
+ "content": "export async function main(subtotal: number) {\n return { subtotal, total: subtotal }\n}\n",
+ "input_transforms": {
+ "subtotal": {
+ "type": "javascript",
+ "expr": "flow_input.subtotal"
+ }
+ }
+ }
+ }
+ ]
+ }
+ }
+ ]
+ }
+}
diff --git a/ai_evals/fixtures/frontend/global/initial/report_digest_script.json b/ai_evals/fixtures/frontend/global/initial/report_digest_script.json
new file mode 100644
index 0000000000..832b06712f
--- /dev/null
+++ b/ai_evals/fixtures/frontend/global/initial/report_digest_script.json
@@ -0,0 +1,23 @@
+{
+ "workspace": {
+ "scripts": [
+ {
+ "path": "f/evals/global/send_report_digest",
+ "summary": "Build and send the eval report digest",
+ "description": "Returns a dry-run summary for eval report digest notifications.",
+ "language": "bun",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "type": "object",
+ "properties": {
+ "dry_run": {
+ "type": "boolean"
+ }
+ },
+ "required": ["dry_run"]
+ },
+ "content": "export async function main(dry_run: boolean) {\n return { dry_run, sent: !dry_run, message: dry_run ? 'Preview digest' : 'Digest sent' }\n}\n"
+ }
+ ]
+ }
+}
diff --git a/ai_evals/modes/app.ts b/ai_evals/modes/app.ts
index 5bca0ad878..af9d7c667b 100644
--- a/ai_evals/modes/app.ts
+++ b/ai_evals/modes/app.ts
@@ -5,15 +5,12 @@ import type { FrontendEvalModelConfig } from "../core/models";
import { validateAppState, type AppFilesState } from "../core/validators";
import type { BenchmarkArtifactFile, ModeRunner } from "../core/types";
import { runAppEval } from "../adapters/frontend/core/app/appEvalRunner";
-import {
- DEFAULT_FRONTEND_EVAL_MODEL,
- getFrontendApiKey,
-} from "./frontendCommon";
-import type { FrontendEvalTransportSettings } from "../core/frontendTransport";
+import { getFrontendApiKey } from "./frontendCommon";
+import type { WindmillBackendSettings } from "../core/windmillBackendSettings";
export function createAppModeRunner(
- modelConfig: FrontendEvalModelConfig = DEFAULT_FRONTEND_EVAL_MODEL,
- transportSettings?: FrontendEvalTransportSettings,
+ modelConfig: FrontendEvalModelConfig,
+ backendSettings: WindmillBackendSettings,
): ModeRunner {
return {
mode: "app",
@@ -37,8 +34,7 @@ export function createAppModeRunner(
appContext: context.evalCase?.runtime?.appContext,
provider: modelConfig.provider,
model: modelConfig.model,
- transport: transportSettings?.transport,
- backend: transportSettings?.backend,
+ backend: backendSettings,
runContext: context,
},
);
diff --git a/ai_evals/modes/flow.ts b/ai_evals/modes/flow.ts
index 4e4f6451b9..e40a495573 100644
--- a/ai_evals/modes/flow.ts
+++ b/ai_evals/modes/flow.ts
@@ -7,11 +7,8 @@ import type { BenchmarkArtifactFile, ModeRunner } from "../core/types";
import { runFlowEval } from "../adapters/frontend/core/flow/flowEvalRunner";
import type { FlowWorkspaceFixtures } from "../adapters/frontend/core/flow/fileHelpers";
import { BackendPreviewClient } from "../adapters/frontend/backendPreview";
-import {
- DEFAULT_FRONTEND_EVAL_MODEL,
- getFrontendApiKey,
-} from "./frontendCommon";
-import type { FrontendEvalTransportSettings } from "../core/frontendTransport";
+import { getFrontendApiKey } from "./frontendCommon";
+import type { WindmillBackendSettings } from "../core/windmillBackendSettings";
import {
normalizeFlowInitialFixture,
normalizeFlowStateFixture,
@@ -19,9 +16,9 @@ import {
} from "./flowFixtures";
export function createFlowModeRunner(
- modelConfig: FrontendEvalModelConfig = DEFAULT_FRONTEND_EVAL_MODEL,
- backendValidation?: BackendValidationSettings,
- transportSettings?: FrontendEvalTransportSettings,
+ modelConfig: FrontendEvalModelConfig,
+ backendValidation: BackendValidationSettings | undefined,
+ backendSettings: WindmillBackendSettings,
): ModeRunner {
return {
mode: "flow",
@@ -49,8 +46,7 @@ export function createFlowModeRunner(
maxIterations: context.evalCase?.runtime?.maxTurns,
provider: modelConfig.provider,
model: modelConfig.model,
- transport: transportSettings?.transport,
- backend: transportSettings?.backend,
+ backend: backendSettings,
runContext: context,
},
);
diff --git a/ai_evals/modes/frontendCommon.test.ts b/ai_evals/modes/frontendCommon.test.ts
index cac10ffcab..897ac3f8a3 100644
--- a/ai_evals/modes/frontendCommon.test.ts
+++ b/ai_evals/modes/frontendCommon.test.ts
@@ -5,12 +5,14 @@ const ORIGINAL_ENV = {
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,
OPENAI_API_KEY: process.env.OPENAI_API_KEY,
GEMINI_API_KEY: process.env.GEMINI_API_KEY,
+ DEEPSEEK_API_KEY: process.env.DEEPSEEK_API_KEY,
};
afterEach(() => {
process.env.ANTHROPIC_API_KEY = ORIGINAL_ENV.ANTHROPIC_API_KEY;
process.env.OPENAI_API_KEY = ORIGINAL_ENV.OPENAI_API_KEY;
process.env.GEMINI_API_KEY = ORIGINAL_ENV.GEMINI_API_KEY;
+ process.env.DEEPSEEK_API_KEY = ORIGINAL_ENV.DEEPSEEK_API_KEY;
});
describe("getFrontendApiKey", () => {
@@ -19,10 +21,15 @@ describe("getFrontendApiKey", () => {
expect(getFrontendApiKey("googleai")).toBe("gemini-test-key");
});
+ it("reads the DeepSeek API key for deepseek models", () => {
+ process.env.DEEPSEEK_API_KEY = "deepseek-test-key";
+ expect(getFrontendApiKey("deepseek")).toBe("deepseek-test-key");
+ });
+
it("throws a provider-specific error when the key is missing", () => {
delete process.env.GEMINI_API_KEY;
expect(() => getFrontendApiKey("googleai")).toThrow(
- "GEMINI_API_KEY is required for frontend evals"
+ "GEMINI_API_KEY is required for frontend evals",
);
});
});
diff --git a/ai_evals/modes/frontendCommon.ts b/ai_evals/modes/frontendCommon.ts
index 2619d21821..b81907b42d 100644
--- a/ai_evals/modes/frontendCommon.ts
+++ b/ai_evals/modes/frontendCommon.ts
@@ -1,20 +1,16 @@
-import {
- getFrontendEvalModel,
- resolveEvalModel,
- type FrontendEvalModelConfig,
-} from "../core/models";
+import type { FrontendEvalModelConfig } from "../core/models";
-export const DEFAULT_FRONTEND_EVAL_MODEL: FrontendEvalModelConfig = getFrontendEvalModel(
- resolveEvalModel("flow")
-);
-
-export function getFrontendApiKey(provider: FrontendEvalModelConfig["provider"]): string {
+export function getFrontendApiKey(
+ provider: FrontendEvalModelConfig["provider"],
+): string {
const envName =
provider === "anthropic"
? "ANTHROPIC_API_KEY"
: provider === "googleai"
? "GEMINI_API_KEY"
- : "OPENAI_API_KEY";
+ : provider === "deepseek"
+ ? "DEEPSEEK_API_KEY"
+ : "OPENAI_API_KEY";
const apiKey = process.env[envName];
if (!apiKey) {
throw new Error(`${envName} is required for frontend evals`);
diff --git a/ai_evals/modes/global.ts b/ai_evals/modes/global.ts
new file mode 100644
index 0000000000..f3cbf6fd86
--- /dev/null
+++ b/ai_evals/modes/global.ts
@@ -0,0 +1,87 @@
+import { readFile } from "node:fs/promises";
+import {
+ runGlobalEval,
+ type GlobalLiveEditorDraftFixture,
+} from "../adapters/frontend/core/global/globalEvalRunner";
+import type { BenchmarkWorkspaceRunnables } from "../adapters/frontend/mockBackend";
+import type { FrontendEvalModelConfig } from "../core/models";
+import type { BenchmarkArtifactFile, GlobalValidationSpec, ModeRunner } from "../core/types";
+import { validateGlobalState, type GlobalDraftState } from "../core/validators";
+import type { WindmillBackendSettings } from "../core/windmillBackendSettings";
+import { getFrontendApiKey } from "./frontendCommon";
+
+export interface GlobalInitialFixture {
+ workspace?: BenchmarkWorkspaceRunnables;
+ liveEditorDrafts?: GlobalLiveEditorDraftFixture[];
+}
+
+export function createGlobalModeRunner(
+ modelConfig: FrontendEvalModelConfig,
+ backendSettings: WindmillBackendSettings,
+): ModeRunner {
+ return {
+ mode: "global",
+ concurrency: 3,
+ judgeThreshold: 80,
+ async loadInitial(path) {
+ return path ? await loadGlobalInitialFixture(path) : undefined;
+ },
+ async loadExpected(path) {
+ return path ? await loadGlobalExpectedFixture(path) : undefined;
+ },
+ async run(prompt, initial, context) {
+ const result = await runGlobalEval(
+ prompt,
+ getFrontendApiKey(modelConfig.provider),
+ {
+ workspaceFixtures: initial?.workspace,
+ liveEditorDrafts: initial?.liveEditorDrafts,
+ maxIterations: context.evalCase?.runtime?.maxTurns,
+ provider: modelConfig.provider,
+ model: modelConfig.model,
+ backend: backendSettings,
+ runContext: context,
+ },
+ );
+
+ return {
+ success: result.success,
+ actual: result.state,
+ error: result.error,
+ assistantMessageCount: result.assistantMessageCount,
+ toolCallCount: result.toolCallCount,
+ toolsUsed: result.toolsUsed,
+ toolCallDetails: result.toolCallDetails,
+ skillsInvoked: [],
+ tokenUsage: result.tokenUsage,
+ };
+ },
+ validate({ evalCase, actual, expected }) {
+ return validateGlobalState({
+ actual,
+ expected,
+ validate: evalCase.validate as GlobalValidationSpec | undefined,
+ });
+ },
+ buildArtifacts(actual): BenchmarkArtifactFile[] {
+ return [
+ {
+ path: "global-drafts.json",
+ content: JSON.stringify(actual, null, 2) + "\n",
+ },
+ ];
+ },
+ };
+}
+
+async function loadGlobalInitialFixture(path: string): Promise {
+ const parsed = JSON.parse(await readFile(path, "utf8")) as GlobalInitialFixture;
+ return {
+ workspace: parsed.workspace ?? {},
+ liveEditorDrafts: parsed.liveEditorDrafts ?? [],
+ };
+}
+
+async function loadGlobalExpectedFixture(path: string): Promise {
+ return JSON.parse(await readFile(path, "utf8")) as GlobalDraftState;
+}
diff --git a/ai_evals/modes/script.ts b/ai_evals/modes/script.ts
index 7671e8220a..0c49b05d7d 100644
--- a/ai_evals/modes/script.ts
+++ b/ai_evals/modes/script.ts
@@ -6,16 +6,13 @@ import type { BenchmarkArtifactFile, ModeRunner } from "../core/types";
import { BackendPreviewClient } from "../adapters/frontend/backendPreview";
import { runScriptEval } from "../adapters/frontend/core/script/scriptEvalRunner";
import type { ScriptEvalState } from "../adapters/frontend/core/script/fileHelpers";
-import {
- DEFAULT_FRONTEND_EVAL_MODEL,
- getFrontendApiKey,
-} from "./frontendCommon";
-import type { FrontendEvalTransportSettings } from "../core/frontendTransport";
+import { getFrontendApiKey } from "./frontendCommon";
+import type { WindmillBackendSettings } from "../core/windmillBackendSettings";
export function createScriptModeRunner(
- modelConfig: FrontendEvalModelConfig = DEFAULT_FRONTEND_EVAL_MODEL,
- backendValidation?: BackendValidationSettings,
- transportSettings?: FrontendEvalTransportSettings,
+ modelConfig: FrontendEvalModelConfig,
+ backendValidation: BackendValidationSettings | undefined,
+ backendSettings: WindmillBackendSettings,
): ModeRunner {
return {
mode: "script",
@@ -40,8 +37,7 @@ export function createScriptModeRunner(
maxIterations: context.evalCase?.runtime?.maxTurns,
provider: modelConfig.provider,
model: modelConfig.model,
- transport: transportSettings?.transport,
- backend: transportSettings?.backend,
+ backend: backendSettings,
runContext: context,
},
);
diff --git a/backend/.sqlx/query-0142d9dc9c1b57487dd5709a0376794f18d33e5bd6340c0189be7818cda64328.json b/backend/.sqlx/query-0142d9dc9c1b57487dd5709a0376794f18d33e5bd6340c0189be7818cda64328.json
new file mode 100644
index 0000000000..461d1afb14
--- /dev/null
+++ b/backend/.sqlx/query-0142d9dc9c1b57487dd5709a0376794f18d33e5bd6340c0189be7818cda64328.json
@@ -0,0 +1,94 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "SELECT email, login_type::TEXT, super_admin, devops, verified, name, company, username, NULL::bool as operator_only, first_time_user, role_source, disabled, NULL::text as workspace_id FROM password WHERE email = $1",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "email",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 1,
+ "name": "login_type",
+ "type_info": "Text"
+ },
+ {
+ "ordinal": 2,
+ "name": "super_admin",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 3,
+ "name": "devops",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 4,
+ "name": "verified",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 5,
+ "name": "name",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 6,
+ "name": "company",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 7,
+ "name": "username",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 8,
+ "name": "operator_only",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 9,
+ "name": "first_time_user",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 10,
+ "name": "role_source",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 11,
+ "name": "disabled",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 12,
+ "name": "workspace_id",
+ "type_info": "Text"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Text"
+ ]
+ },
+ "nullable": [
+ false,
+ null,
+ false,
+ false,
+ false,
+ true,
+ true,
+ true,
+ null,
+ false,
+ false,
+ false,
+ null
+ ]
+ },
+ "hash": "0142d9dc9c1b57487dd5709a0376794f18d33e5bd6340c0189be7818cda64328"
+}
diff --git a/backend/.sqlx/query-6d992a933bb878733b7afd7a4295b9ad6f5276b60ce20e0378d6148976e02777.json b/backend/.sqlx/query-04409657066c624308954958d9dd451452efc25e57769fb94b771d4879150835.json
similarity index 54%
rename from backend/.sqlx/query-6d992a933bb878733b7afd7a4295b9ad6f5276b60ce20e0378d6148976e02777.json
rename to backend/.sqlx/query-04409657066c624308954958d9dd451452efc25e57769fb94b771d4879150835.json
index e32c1e06ed..9c88e54c21 100644
--- a/backend/.sqlx/query-6d992a933bb878733b7afd7a4295b9ad6f5276b60ce20e0378d6148976e02777.json
+++ b/backend/.sqlx/query-04409657066c624308954958d9dd451452efc25e57769fb94b771d4879150835.json
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
- "query": "\n SELECT\n flow_version.id AS version,\n flow_version.value->>'early_return' as early_return,\n flow_version.value->>'preprocessor_module' IS NOT NULL as has_preprocessor,\n (flow_version.value->>'chat_input_enabled')::boolean as chat_input_enabled,\n flow.tag,\n flow.dedicated_worker,\n flow.on_behalf_of_email,\n flow.edited_by,\n flow.labels\n FROM\n flow_version\n INNER JOIN flow\n ON flow.path = flow_version.path AND\n flow.workspace_id = flow_version.workspace_id\n WHERE\n flow_version.workspace_id = $1 AND\n flow_version.path = $2 AND\n flow_version.id = $3\n ",
+ "query": "\n SELECT\n flow_version.id AS version,\n flow_version.value->>'early_return' as early_return,\n flow_version.value->>'preprocessor_module' IS NOT NULL as has_preprocessor,\n flow_version.value->>'failure_module' IS NOT NULL as has_failure_module,\n (flow_version.value->>'chat_input_enabled')::boolean as chat_input_enabled,\n flow.tag,\n flow.dedicated_worker,\n flow.on_behalf_of_email,\n flow.edited_by,\n flow.labels\n FROM\n flow_version\n INNER JOIN flow\n ON flow.path = flow_version.path AND\n flow.workspace_id = flow_version.workspace_id\n WHERE\n flow_version.workspace_id = $1 AND\n flow_version.path = $2 AND\n flow_version.id = $3\n ",
"describe": {
"columns": [
{
@@ -20,31 +20,36 @@
},
{
"ordinal": 3,
- "name": "chat_input_enabled",
+ "name": "has_failure_module",
"type_info": "Bool"
},
{
"ordinal": 4,
+ "name": "chat_input_enabled",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 5,
"name": "tag",
"type_info": "Varchar"
},
{
- "ordinal": 5,
+ "ordinal": 6,
"name": "dedicated_worker",
"type_info": "Bool"
},
{
- "ordinal": 6,
+ "ordinal": 7,
"name": "on_behalf_of_email",
"type_info": "Text"
},
{
- "ordinal": 7,
+ "ordinal": 8,
"name": "edited_by",
"type_info": "Varchar"
},
{
- "ordinal": 8,
+ "ordinal": 9,
"name": "labels",
"type_info": "TextArray"
}
@@ -61,6 +66,7 @@
null,
null,
null,
+ null,
true,
true,
true,
@@ -68,5 +74,5 @@
true
]
},
- "hash": "6d992a933bb878733b7afd7a4295b9ad6f5276b60ce20e0378d6148976e02777"
+ "hash": "04409657066c624308954958d9dd451452efc25e57769fb94b771d4879150835"
}
diff --git a/backend/.sqlx/query-0476ae2245aa678a50c5fd04cdee32cc151e29b177cc85caa088430b16336373.json b/backend/.sqlx/query-0476ae2245aa678a50c5fd04cdee32cc151e29b177cc85caa088430b16336373.json
new file mode 100644
index 0000000000..5ae11837c7
--- /dev/null
+++ b/backend/.sqlx/query-0476ae2245aa678a50c5fd04cdee32cc151e29b177cc85caa088430b16336373.json
@@ -0,0 +1,23 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "SELECT flow_version.path FROM flow_version\n INNER JOIN flow\n ON flow.path = flow_version.path AND\n flow.workspace_id = flow_version.workspace_id\n WHERE flow_version.id = $1 AND flow_version.workspace_id = $2",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "path",
+ "type_info": "Varchar"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Int8",
+ "Text"
+ ]
+ },
+ "nullable": [
+ false
+ ]
+ },
+ "hash": "0476ae2245aa678a50c5fd04cdee32cc151e29b177cc85caa088430b16336373"
+}
diff --git a/backend/.sqlx/query-04e584128fc74dd57ca7e6c3b59c446bc8610101e92d99c4733b79a1141444e8.json b/backend/.sqlx/query-04e584128fc74dd57ca7e6c3b59c446bc8610101e92d99c4733b79a1141444e8.json
new file mode 100644
index 0000000000..84919cc20e
--- /dev/null
+++ b/backend/.sqlx/query-04e584128fc74dd57ca7e6c3b59c446bc8610101e92d99c4733b79a1141444e8.json
@@ -0,0 +1,12 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, enabled,\n script_path, args, is_flow, email, timezone, summary, permissioned_as)\n VALUES\n ('test-workspace', 'f/sch/runtime_only', 'test-user', NOW(), '0 * * * * *', true,\n 'f/scripts/x', '{}', false, 'test@windmill.dev', 'UTC', 'sch', 'u/test-user'),\n ('wm-fork-test-workspace', 'f/sch/runtime_only', 'test-user', NOW(), '0 * * * * *', false,\n 'f/scripts/x', '{}', false, 'test@windmill.dev', 'UTC', 'sch', 'u/test-user')",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": []
+ },
+ "nullable": []
+ },
+ "hash": "04e584128fc74dd57ca7e6c3b59c446bc8610101e92d99c4733b79a1141444e8"
+}
diff --git a/backend/.sqlx/query-bd000b7ab6438826093ba556af819741b493a91babaeb103852d3912f520719f.json b/backend/.sqlx/query-074dc85ffb596585eb99336ffa34fa29fc0a1aff4d10d6fe4aba4b5357afb0f3.json
similarity index 92%
rename from backend/.sqlx/query-bd000b7ab6438826093ba556af819741b493a91babaeb103852d3912f520719f.json
rename to backend/.sqlx/query-074dc85ffb596585eb99336ffa34fa29fc0a1aff4d10d6fe4aba4b5357afb0f3.json
index 42b90ef229..a932976a2f 100644
--- a/backend/.sqlx/query-bd000b7ab6438826093ba556af819741b493a91babaeb103852d3912f520719f.json
+++ b/backend/.sqlx/query-074dc85ffb596585eb99336ffa34fa29fc0a1aff4d10d6fe4aba4b5357afb0f3.json
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
- "query": "\n SELECT\n j.id AS \"id!\", j.workspace_id AS \"workspace_id!\", j.parent_job, j.flow_step_id IS NOT NULL AS \"is_flow_step?\",\n COALESCE(s.flow_status, s.workflow_as_code_status) AS \"flow_status: Box\", r.ping AS last_ping, j.same_worker AS \"same_worker?\",\n q.worker AS \"worker?\",\n wp.ping_at AS \"worker_last_ping?\",\n wp.memory_usage AS \"worker_memory_usage?\",\n wp.wm_memory_usage AS \"worker_wm_memory_usage?\",\n wp.memory AS \"worker_memory_total?\",\n wp.worker_group AS \"worker_group?\",\n wp.wm_version AS \"worker_version?\",\n wp.current_job_id AS \"worker_current_job_id?\",\n wp.worker_instance AS \"worker_instance?\"\n FROM v2_job_queue q JOIN v2_job j USING (id) LEFT JOIN v2_job_runtime r USING (id) LEFT JOIN v2_job_status s USING (id)\n LEFT JOIN worker_ping wp ON wp.worker = q.worker\n WHERE q.running = true AND q.suspend = 0 AND q.suspend_until IS null AND q.scheduled_for <= now()\n AND (j.kind = 'flow' OR j.kind = 'flowpreview' OR j.kind = 'flownode')\n AND r.ping IS NOT NULL AND r.ping < NOW() - ($1 || ' seconds')::interval\n AND q.canceled_by IS NULL\n\n ",
+ "query": "\n SELECT\n j.id AS \"id!\", j.workspace_id AS \"workspace_id!\", j.parent_job, j.flow_step_id IS NOT NULL AS \"is_flow_step?\",\n COALESCE(s.flow_status, s.workflow_as_code_status) AS \"flow_status: Box\", r.ping AS last_ping, j.same_worker AS \"same_worker?\",\n q.worker AS \"worker?\",\n wp.ping_at AS \"worker_last_ping?\",\n wp.memory_usage AS \"worker_memory_usage?\",\n wp.wm_memory_usage AS \"worker_wm_memory_usage?\",\n wp.memory AS \"worker_memory_total?\",\n wp.worker_group AS \"worker_group?\",\n wp.wm_version AS \"worker_version?\",\n wp.current_job_id AS \"worker_current_job_id?\",\n wp.worker_instance AS \"worker_instance?\"\n FROM v2_job_queue q JOIN v2_job j USING (id) LEFT JOIN v2_job_runtime r USING (id) LEFT JOIN v2_job_status s USING (id)\n LEFT JOIN worker_ping wp ON wp.worker = q.worker\n WHERE q.running = true AND q.suspend = 0 AND q.suspend_until IS null AND q.scheduled_for <= now()\n AND (j.kind = 'flow' OR j.kind = 'flowpreview' OR j.kind = 'flownode' OR j.kind = 'singlestepflow')\n AND r.ping IS NOT NULL AND r.ping < NOW() - ($1 || ' seconds')::interval\n AND q.canceled_by IS NULL\n\n ",
"describe": {
"columns": [
{
@@ -108,5 +108,5 @@
false
]
},
- "hash": "bd000b7ab6438826093ba556af819741b493a91babaeb103852d3912f520719f"
+ "hash": "074dc85ffb596585eb99336ffa34fa29fc0a1aff4d10d6fe4aba4b5357afb0f3"
}
diff --git a/backend/.sqlx/query-081e56844a17e37145b018eda8c9f2b3927a8d46c90959af4c38ff46b8f62e2d.json b/backend/.sqlx/query-081e56844a17e37145b018eda8c9f2b3927a8d46c90959af4c38ff46b8f62e2d.json
new file mode 100644
index 0000000000..73881e1590
--- /dev/null
+++ b/backend/.sqlx/query-081e56844a17e37145b018eda8c9f2b3927a8d46c90959af4c38ff46b8f62e2d.json
@@ -0,0 +1,12 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, enabled,\n script_path, args, is_flow, email, timezone, summary, permissioned_as)\n VALUES\n ('test-workspace', 'f/sch/config_change', 'test-user', NOW(), '0 * * * * *', false,\n 'f/scripts/parent_path', '{}', false, 'test@windmill.dev', 'UTC', 'sch', 'u/test-user'),\n ('wm-fork-test-workspace', 'f/sch/config_change', 'test-user', NOW(), '0 * * * * *', false,\n 'f/scripts/fork_path', '{}', false, 'test@windmill.dev', 'UTC', 'sch', 'u/test-user')",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": []
+ },
+ "nullable": []
+ },
+ "hash": "081e56844a17e37145b018eda8c9f2b3927a8d46c90959af4c38ff46b8f62e2d"
+}
diff --git a/backend/.sqlx/query-09713562019ede9622378e9d23f9bb36ad10832a79678723d9e6550b89383217.json b/backend/.sqlx/query-09713562019ede9622378e9d23f9bb36ad10832a79678723d9e6550b89383217.json
new file mode 100644
index 0000000000..4371654b99
--- /dev/null
+++ b/backend/.sqlx/query-09713562019ede9622378e9d23f9bb36ad10832a79678723d9e6550b89383217.json
@@ -0,0 +1,12 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "INSERT INTO http_trigger (workspace_id, path, edited_by, edited_at, route_path,\n route_path_key, script_path, is_flow, http_method, request_type,\n authentication_method, mode, permissioned_as)\n VALUES\n ('test-workspace', 'f/rt/config_change', 'test-user', NOW(), 'parent', 'parent',\n 'f/scripts/y', false, 'get', 'sync',\n 'none', 'disabled', 'u/test-user'),\n ('wm-fork-test-workspace', 'f/rt/config_change', 'test-user', NOW(), 'fork', 'fork',\n 'f/scripts/y', false, 'get', 'sync',\n 'none', 'disabled', 'u/test-user')",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": []
+ },
+ "nullable": []
+ },
+ "hash": "09713562019ede9622378e9d23f9bb36ad10832a79678723d9e6550b89383217"
+}
diff --git a/backend/.sqlx/query-54b7d6614bdfae5d9113c1baf9eae6e1f600c0593ba855138b435561687905c5.json b/backend/.sqlx/query-097cdca7a20e174d4db1604c087d5d31e454eb6889c4a9bd6e857bd71d91d2bd.json
similarity index 50%
rename from backend/.sqlx/query-54b7d6614bdfae5d9113c1baf9eae6e1f600c0593ba855138b435561687905c5.json
rename to backend/.sqlx/query-097cdca7a20e174d4db1604c087d5d31e454eb6889c4a9bd6e857bd71d91d2bd.json
index 7657331ba1..e9d117caff 100644
--- a/backend/.sqlx/query-54b7d6614bdfae5d9113c1baf9eae6e1f600c0593ba855138b435561687905c5.json
+++ b/backend/.sqlx/query-097cdca7a20e174d4db1604c087d5d31e454eb6889c4a9bd6e857bd71d91d2bd.json
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
- "query": "\nSELECT COALESCE(\n (SELECT COUNT(*) \n FROM jsonb_object_keys(job_uuids) AS keys(key) \n WHERE key <> $2),\n 0\n)\nFROM concurrency_counter\nWHERE concurrency_id = $1\nFOR UPDATE",
+ "query": "\nSELECT COALESCE(\n (SELECT COUNT(*)\n FROM jsonb_object_keys(job_uuids) AS keys(key)\n WHERE key <> $2),\n 0\n)\nFROM concurrency_counter\nWHERE concurrency_id = $1\nFOR UPDATE",
"describe": {
"columns": [
{
@@ -19,5 +19,5 @@
null
]
},
- "hash": "54b7d6614bdfae5d9113c1baf9eae6e1f600c0593ba855138b435561687905c5"
+ "hash": "097cdca7a20e174d4db1604c087d5d31e454eb6889c4a9bd6e857bd71d91d2bd"
}
diff --git a/backend/.sqlx/query-0c89ef278782f5a72b0b07ab3ba0edc487f03edd61936fcf77dee93fb22839ea.json b/backend/.sqlx/query-0c89ef278782f5a72b0b07ab3ba0edc487f03edd61936fcf77dee93fb22839ea.json
deleted file mode 100644
index e74e12f501..0000000000
--- a/backend/.sqlx/query-0c89ef278782f5a72b0b07ab3ba0edc487f03edd61936fcf77dee93fb22839ea.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "db_name": "PostgreSQL",
- "query": "SELECT jsonb_build_object(\n 'kind', jb.kind,\n 'script_path', jb.runnable_path,\n 'latest_schema', COALESCE(\n (SELECT DISTINCT ON (s.path) s.schema FROM script s WHERE s.workspace_id = $1 AND s.path = jb.runnable_path AND jb.kind = 'script' ORDER BY s.path, s.created_at DESC),\n (SELECT flow_version.schema FROM flow LEFT JOIN flow_version ON flow_version.id = flow.versions[array_upper(flow.versions, 1)] WHERE flow.workspace_id = $1 AND flow.path = jb.runnable_path AND jb.kind = 'flow')\n ),\n 'schemas', ARRAY(\n SELECT jsonb_build_object(\n 'script_hash', LPAD(TO_HEX(COALESCE(s.hash, f.id)), 16, '0'),\n 'job_ids', ARRAY_AGG(DISTINCT j.id),\n 'schema', (ARRAY_AGG(COALESCE(s.schema, f.schema)))[1]\n ) FROM v2_job j\n LEFT JOIN script s ON s.hash = j.runnable_id AND j.kind = 'script'\n LEFT JOIN flow_version f ON f.id = j.runnable_id AND j.kind = 'flow'\n WHERE j.id = ANY(ARRAY_AGG(jb.id))\n GROUP BY COALESCE(s.hash, f.id)\n )\n ) FROM v2_job jb\n WHERE (jb.kind = 'flow' OR jb.kind = 'script')\n AND jb.workspace_id = $1 AND jb.id = ANY($2)\n GROUP BY jb.kind, jb.runnable_path",
- "describe": {
- "columns": [
- {
- "ordinal": 0,
- "name": "jsonb_build_object",
- "type_info": "Jsonb"
- }
- ],
- "parameters": {
- "Left": [
- "Text",
- "UuidArray"
- ]
- },
- "nullable": [
- null
- ]
- },
- "hash": "0c89ef278782f5a72b0b07ab3ba0edc487f03edd61936fcf77dee93fb22839ea"
-}
diff --git a/backend/.sqlx/query-0cca68f11329cd41ab9372297b715af008ea7db408f49cf525656d6224092429.json b/backend/.sqlx/query-0cca68f11329cd41ab9372297b715af008ea7db408f49cf525656d6224092429.json
new file mode 100644
index 0000000000..ccad8d570d
--- /dev/null
+++ b/backend/.sqlx/query-0cca68f11329cd41ab9372297b715af008ea7db408f49cf525656d6224092429.json
@@ -0,0 +1,28 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "SELECT operator, is_admin FROM usr WHERE email = $1 AND is_service_account IS true LIMIT 1",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "operator",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 1,
+ "name": "is_admin",
+ "type_info": "Bool"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Text"
+ ]
+ },
+ "nullable": [
+ false,
+ false
+ ]
+ },
+ "hash": "0cca68f11329cd41ab9372297b715af008ea7db408f49cf525656d6224092429"
+}
diff --git a/backend/.sqlx/query-0f5ec10de91deac2d40b1e8a6dad0e9341f57cd060e7f228b40634469269c407.json b/backend/.sqlx/query-0f5ec10de91deac2d40b1e8a6dad0e9341f57cd060e7f228b40634469269c407.json
new file mode 100644
index 0000000000..5a1875441a
--- /dev/null
+++ b/backend/.sqlx/query-0f5ec10de91deac2d40b1e8a6dad0e9341f57cd060e7f228b40634469269c407.json
@@ -0,0 +1,42 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "WITH bounds AS (\n SELECT ($2::bigint % 4294967296)::text::xid AS prev_xid,\n ($3::bigint % 4294967296)::text::xid AS cur_xid,\n $1::timestamptz AS ts_floor\n ),\n batch AS (\n SELECT workspace_id, id, timestamp, username, operation,\n action_kind::text AS action_kind, resource, parameters, email, span\n FROM audit_partitioned, bounds b\n WHERE timestamp >= b.ts_floor\n AND age(xmin) > age(b.cur_xid)\n AND age(xmin) <= age(b.prev_xid)\n ORDER BY id\n )\n SELECT to_char(timestamp AT TIME ZONE 'UTC', 'YYYY-MM-DD') AS \"day!\",\n string_agg(row_to_json(batch)::text, E'\\n' ORDER BY id) AS \"ndjson!\",\n max(id) AS \"max_id!\",\n max(timestamp) AS \"max_ts!\"\n FROM batch\n GROUP BY 1\n ORDER BY 1",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "day!",
+ "type_info": "Text"
+ },
+ {
+ "ordinal": 1,
+ "name": "ndjson!",
+ "type_info": "Text"
+ },
+ {
+ "ordinal": 2,
+ "name": "max_id!",
+ "type_info": "Int8"
+ },
+ {
+ "ordinal": 3,
+ "name": "max_ts!",
+ "type_info": "Timestamptz"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Timestamptz",
+ "Int8",
+ "Int8"
+ ]
+ },
+ "nullable": [
+ null,
+ null,
+ null,
+ null
+ ]
+ },
+ "hash": "0f5ec10de91deac2d40b1e8a6dad0e9341f57cd060e7f228b40634469269c407"
+}
diff --git a/backend/.sqlx/query-14bc9dd1d02a3d121297509beacc27f3c29d1b3877c1f2e7c206f0e36ef18701.json b/backend/.sqlx/query-14bc9dd1d02a3d121297509beacc27f3c29d1b3877c1f2e7c206f0e36ef18701.json
new file mode 100644
index 0000000000..6310017f18
--- /dev/null
+++ b/backend/.sqlx/query-14bc9dd1d02a3d121297509beacc27f3c29d1b3877c1f2e7c206f0e36ef18701.json
@@ -0,0 +1,40 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "\n SELECT\n (elem->>'installation_id')::bigint as installation_id,\n elem->>'account_id' as account_id,\n elem->>'github_base_url' as github_base_url,\n COALESCE((elem->>'provisioned_by_admin')::bool, false) as \"provisioned_by_admin!\"\n FROM workspace_settings,\n LATERAL jsonb_array_elements(git_app_installations) AS elem\n WHERE workspace_id = $1\n ",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "installation_id",
+ "type_info": "Int8"
+ },
+ {
+ "ordinal": 1,
+ "name": "account_id",
+ "type_info": "Text"
+ },
+ {
+ "ordinal": 2,
+ "name": "github_base_url",
+ "type_info": "Text"
+ },
+ {
+ "ordinal": 3,
+ "name": "provisioned_by_admin!",
+ "type_info": "Bool"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Text"
+ ]
+ },
+ "nullable": [
+ null,
+ null,
+ null,
+ null
+ ]
+ },
+ "hash": "14bc9dd1d02a3d121297509beacc27f3c29d1b3877c1f2e7c206f0e36ef18701"
+}
diff --git a/backend/.sqlx/query-14c2784a68f06e7349941671abe5a9108b6f32490cb61b14de71102bb305f71d.json b/backend/.sqlx/query-14c2784a68f06e7349941671abe5a9108b6f32490cb61b14de71102bb305f71d.json
new file mode 100644
index 0000000000..d7ef4e04e0
--- /dev/null
+++ b/backend/.sqlx/query-14c2784a68f06e7349941671abe5a9108b6f32490cb61b14de71102bb305f71d.json
@@ -0,0 +1,24 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "WITH RECURSIVE chain(id, parent_job, flow_innermost_root_job, runnable_id, runnable_path, raw_flow, depth) AS (\n SELECT id, parent_job, flow_innermost_root_job, runnable_id, runnable_path, raw_flow, 0\n FROM v2_job\n WHERE id = $1 AND workspace_id = $2\n UNION ALL\n SELECT j.id, j.parent_job, j.flow_innermost_root_job, j.runnable_id, j.runnable_path, j.raw_flow, c.depth + 1\n FROM v2_job j\n JOIN chain c\n ON j.id = COALESCE(c.flow_innermost_root_job, c.parent_job)\n WHERE j.workspace_id = $2 AND c.depth < $3\n )\n SELECT\n CASE\n WHEN flow_version.id IS NOT NULL THEN\n flow_version.value -> 'flow_env'\n ELSE\n chain.raw_flow -> 'flow_env'\n END AS \"flow_env: Json>>\"\n FROM chain\n LEFT JOIN flow_version\n ON flow_version.id = chain.runnable_id\n AND flow_version.path = chain.runnable_path\n AND flow_version.workspace_id = $2\n WHERE (CASE\n WHEN flow_version.id IS NOT NULL THEN flow_version.value -> 'flow_env'\n ELSE chain.raw_flow -> 'flow_env'\n END) IS NOT NULL\n ORDER BY chain.depth ASC\n LIMIT 1",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "flow_env: Json>>",
+ "type_info": "Jsonb"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Uuid",
+ "Text",
+ "Int4"
+ ]
+ },
+ "nullable": [
+ null
+ ]
+ },
+ "hash": "14c2784a68f06e7349941671abe5a9108b6f32490cb61b14de71102bb305f71d"
+}
diff --git a/backend/.sqlx/query-16b4496c21d0619dab4521dca22e5fe144c59156a8f06d8592291684c49b2f37.json b/backend/.sqlx/query-16b4496c21d0619dab4521dca22e5fe144c59156a8f06d8592291684c49b2f37.json
new file mode 100644
index 0000000000..023eaaa010
--- /dev/null
+++ b/backend/.sqlx/query-16b4496c21d0619dab4521dca22e5fe144c59156a8f06d8592291684c49b2f37.json
@@ -0,0 +1,95 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "SELECT email as \"email!\", login_type::text, verified as \"verified!\", super_admin as \"super_admin!\", devops as \"devops!\", name, company, username, NULL::bool as operator_only, first_time_user as \"first_time_user!\", role_source as \"role_source!\", disabled as \"disabled!\", NULL::text as workspace_id FROM password\n UNION ALL\n SELECT email as \"email!\", 'service_account'::text as login_type, true as \"verified!\", false as \"super_admin!\", false as \"devops!\", NULL::text as name, NULL::text as company, username, true as operator_only, false as \"first_time_user!\", 'service_account'::text as \"role_source!\", disabled as \"disabled!\", workspace_id\n FROM usr\n WHERE is_service_account IS true\n ORDER BY \"super_admin!\" DESC, \"devops!\" DESC, \"email!\"\n LIMIT $1 OFFSET $2",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "email!",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 1,
+ "name": "login_type",
+ "type_info": "Text"
+ },
+ {
+ "ordinal": 2,
+ "name": "verified!",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 3,
+ "name": "super_admin!",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 4,
+ "name": "devops!",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 5,
+ "name": "name",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 6,
+ "name": "company",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 7,
+ "name": "username",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 8,
+ "name": "operator_only",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 9,
+ "name": "first_time_user!",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 10,
+ "name": "role_source!",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 11,
+ "name": "disabled!",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 12,
+ "name": "workspace_id",
+ "type_info": "Text"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Int8",
+ "Int8"
+ ]
+ },
+ "nullable": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ]
+ },
+ "hash": "16b4496c21d0619dab4521dca22e5fe144c59156a8f06d8592291684c49b2f37"
+}
diff --git a/backend/.sqlx/query-19b59c478744d029c6006b01f04243ad2e0aef485a780daea5d76b0be2bb2ea2.json b/backend/.sqlx/query-19b59c478744d029c6006b01f04243ad2e0aef485a780daea5d76b0be2bb2ea2.json
deleted file mode 100644
index 41520bfc88..0000000000
--- a/backend/.sqlx/query-19b59c478744d029c6006b01f04243ad2e0aef485a780daea5d76b0be2bb2ea2.json
+++ /dev/null
@@ -1,63 +0,0 @@
-{
- "db_name": "PostgreSQL",
- "query": "\n SELECT\n id,\n args as \"args: _\",\n created_at\n FROM v2_job\n WHERE workspace_id = $1\n AND (\n kind = 'unassigned_script'::JOB_KIND OR\n kind = 'unassigned_flow'::JOB_KIND OR\n kind = 'unassigned_singlestepflow'::JOB_KIND\n )\n AND trigger_kind = $2\n AND trigger = $3\n AND id = ANY($4)\n ",
- "describe": {
- "columns": [
- {
- "ordinal": 0,
- "name": "id",
- "type_info": "Uuid"
- },
- {
- "ordinal": 1,
- "name": "args: _",
- "type_info": "Jsonb"
- },
- {
- "ordinal": 2,
- "name": "created_at",
- "type_info": "Timestamptz"
- }
- ],
- "parameters": {
- "Left": [
- "Text",
- {
- "Custom": {
- "name": "job_trigger_kind",
- "kind": {
- "Enum": [
- "webhook",
- "http",
- "websocket",
- "kafka",
- "email",
- "nats",
- "schedule",
- "app",
- "ui",
- "postgres",
- "sqs",
- "gcp",
- "mqtt",
- "nextcloud",
- "google",
- "ci_test",
- "github",
- "azure"
- ]
- }
- }
- },
- "Text",
- "UuidArray"
- ]
- },
- "nullable": [
- false,
- true,
- false
- ]
- },
- "hash": "19b59c478744d029c6006b01f04243ad2e0aef485a780daea5d76b0be2bb2ea2"
-}
diff --git a/backend/.sqlx/query-1d0341bd8de94ab8d34a4bb1bb2005305fb3b29751ec987bece183444e89e7d1.json b/backend/.sqlx/query-1d0341bd8de94ab8d34a4bb1bb2005305fb3b29751ec987bece183444e89e7d1.json
new file mode 100644
index 0000000000..b7492622f7
--- /dev/null
+++ b/backend/.sqlx/query-1d0341bd8de94ab8d34a4bb1bb2005305fb3b29751ec987bece183444e89e7d1.json
@@ -0,0 +1,100 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "SELECT email, login_type::TEXT, super_admin, devops, verified, name, company, username, NULL::bool as operator_only, NULL::bool as is_workspace_admin, first_time_user, role_source, disabled, NULL::text as workspace_id FROM password WHERE email = $1",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "email",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 1,
+ "name": "login_type",
+ "type_info": "Text"
+ },
+ {
+ "ordinal": 2,
+ "name": "super_admin",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 3,
+ "name": "devops",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 4,
+ "name": "verified",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 5,
+ "name": "name",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 6,
+ "name": "company",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 7,
+ "name": "username",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 8,
+ "name": "operator_only",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 9,
+ "name": "is_workspace_admin",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 10,
+ "name": "first_time_user",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 11,
+ "name": "role_source",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 12,
+ "name": "disabled",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 13,
+ "name": "workspace_id",
+ "type_info": "Text"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Text"
+ ]
+ },
+ "nullable": [
+ false,
+ null,
+ false,
+ false,
+ false,
+ true,
+ true,
+ true,
+ null,
+ null,
+ false,
+ false,
+ false,
+ null
+ ]
+ },
+ "hash": "1d0341bd8de94ab8d34a4bb1bb2005305fb3b29751ec987bece183444e89e7d1"
+}
diff --git a/backend/.sqlx/query-290599fc173947acb518344d6fb631af9f524389309f17ef04f70c773b1d5e75.json b/backend/.sqlx/query-290599fc173947acb518344d6fb631af9f524389309f17ef04f70c773b1d5e75.json
new file mode 100644
index 0000000000..ad296e9eda
--- /dev/null
+++ b/backend/.sqlx/query-290599fc173947acb518344d6fb631af9f524389309f17ef04f70c773b1d5e75.json
@@ -0,0 +1,28 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "\n SELECT s.item_kind, s.path\n FROM ws_specific s\n WHERE s.workspace_id = $1\n AND (\n (s.item_kind = 'resource' AND EXISTS (\n SELECT 1 FROM resource r\n WHERE r.workspace_id = s.workspace_id AND r.path = s.path\n ))\n OR (s.item_kind = 'variable' AND EXISTS (\n SELECT 1 FROM variable v\n WHERE v.workspace_id = s.workspace_id AND v.path = s.path\n ))\n )\n ORDER BY s.item_kind, s.path\n ",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "item_kind",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 1,
+ "name": "path",
+ "type_info": "Varchar"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Text"
+ ]
+ },
+ "nullable": [
+ false,
+ false
+ ]
+ },
+ "hash": "290599fc173947acb518344d6fb631af9f524389309f17ef04f70c773b1d5e75"
+}
diff --git a/backend/.sqlx/query-295a88070e1762255cdd7680ba2e7bb2a2ffd66a7c432dd318e73e0f81ea9622.json b/backend/.sqlx/query-295a88070e1762255cdd7680ba2e7bb2a2ffd66a7c432dd318e73e0f81ea9622.json
new file mode 100644
index 0000000000..3e47b7b034
--- /dev/null
+++ b/backend/.sqlx/query-295a88070e1762255cdd7680ba2e7bb2a2ffd66a7c432dd318e73e0f81ea9622.json
@@ -0,0 +1,23 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "INSERT INTO variable\n (workspace_id, path, value, is_secret, description, account, is_oauth, expires_at, labels, edited_by)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": [
+ "Varchar",
+ "Varchar",
+ "Varchar",
+ "Bool",
+ "Varchar",
+ "Int4",
+ "Bool",
+ "Timestamptz",
+ "TextArray",
+ "Varchar"
+ ]
+ },
+ "nullable": []
+ },
+ "hash": "295a88070e1762255cdd7680ba2e7bb2a2ffd66a7c432dd318e73e0f81ea9622"
+}
diff --git a/backend/.sqlx/query-2e35598cb9695b726ee1d2cd5c8364503371a5272d88e970b95760555ab33ce2.json b/backend/.sqlx/query-2e35598cb9695b726ee1d2cd5c8364503371a5272d88e970b95760555ab33ce2.json
new file mode 100644
index 0000000000..2ca417b902
--- /dev/null
+++ b/backend/.sqlx/query-2e35598cb9695b726ee1d2cd5c8364503371a5272d88e970b95760555ab33ce2.json
@@ -0,0 +1,14 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "INSERT INTO script (workspace_id, path, hash, content, summary, description, language, created_by, created_at, archived, schema_validation, ws_error_handler_muted, deleted, extra_perms)\n VALUES ('wm-fork-stale-super', 'f/folder2/myscript', 333333, 'echo 1', '', '', 'bash', 'test-user-2', NOW(), false, false, false, false, $1)",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": [
+ "Jsonb"
+ ]
+ },
+ "nullable": []
+ },
+ "hash": "2e35598cb9695b726ee1d2cd5c8364503371a5272d88e970b95760555ab33ce2"
+}
diff --git a/backend/.sqlx/query-2f166b5575a614b028c3130fc5089353bef40f1cccf31b7775d0e9a800425f4d.json b/backend/.sqlx/query-2f166b5575a614b028c3130fc5089353bef40f1cccf31b7775d0e9a800425f4d.json
new file mode 100644
index 0000000000..5964de4111
--- /dev/null
+++ b/backend/.sqlx/query-2f166b5575a614b028c3130fc5089353bef40f1cccf31b7775d0e9a800425f4d.json
@@ -0,0 +1,23 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "\n SELECT COALESCE((elem->>'provisioned_by_admin')::bool, false) as \"is_admin!\"\n FROM workspace_settings,\n LATERAL jsonb_array_elements(git_app_installations) AS elem\n WHERE workspace_id = $1\n AND (elem->>'installation_id')::bigint = $2\n ",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "is_admin!",
+ "type_info": "Bool"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Text",
+ "Int8"
+ ]
+ },
+ "nullable": [
+ null
+ ]
+ },
+ "hash": "2f166b5575a614b028c3130fc5089353bef40f1cccf31b7775d0e9a800425f4d"
+}
diff --git a/backend/.sqlx/query-2f53576c2ad58abc24617e911e486d7c4b9bdb1e8fb1f7725060990ef8984943.json b/backend/.sqlx/query-2f53576c2ad58abc24617e911e486d7c4b9bdb1e8fb1f7725060990ef8984943.json
deleted file mode 100644
index 8c5f43ab07..0000000000
--- a/backend/.sqlx/query-2f53576c2ad58abc24617e911e486d7c4b9bdb1e8fb1f7725060990ef8984943.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
- "db_name": "PostgreSQL",
- "query": "\n SELECT\n CASE\n WHEN flow_version.id IS NOT NULL THEN\n flow_version.value -> 'flow_env' -> $3\n ELSE\n root_job.raw_flow -> 'flow_env' -> $3\n END AS \"flow_env: sqlx::types::Json>\"\n FROM\n v2_job current_job\n JOIN\n v2_job root_job ON root_job.id = COALESCE(current_job.root_job, current_job.flow_innermost_root_job, current_job.parent_job, current_job.id)\n AND root_job.workspace_id = current_job.workspace_id\n LEFT JOIN\n flow_version ON flow_version.id = root_job.runnable_id\n AND flow_version.path = root_job.runnable_path\n AND flow_version.workspace_id = root_job.workspace_id\n WHERE\n current_job.id = $1 AND\n current_job.workspace_id = $2",
- "describe": {
- "columns": [
- {
- "ordinal": 0,
- "name": "flow_env: sqlx::types::Json>",
- "type_info": "Jsonb"
- }
- ],
- "parameters": {
- "Left": [
- "Uuid",
- "Text",
- "Text"
- ]
- },
- "nullable": [
- null
- ]
- },
- "hash": "2f53576c2ad58abc24617e911e486d7c4b9bdb1e8fb1f7725060990ef8984943"
-}
diff --git a/backend/.sqlx/query-31445efb75a7b706f4404c411a4ef6a9ed6d29a02bb7fd08f2ceb0551ecac640.json b/backend/.sqlx/query-31445efb75a7b706f4404c411a4ef6a9ed6d29a02bb7fd08f2ceb0551ecac640.json
new file mode 100644
index 0000000000..b7a32466b0
--- /dev/null
+++ b/backend/.sqlx/query-31445efb75a7b706f4404c411a4ef6a9ed6d29a02bb7fd08f2ceb0551ecac640.json
@@ -0,0 +1,14 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms, summary, created_by)\n VALUES ('wm-fork-visibility-test', 'folder2', 'folder2', ARRAY['u/test-user-2']::varchar[], $1, '', 'test-user-2')",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": [
+ "Jsonb"
+ ]
+ },
+ "nullable": []
+ },
+ "hash": "31445efb75a7b706f4404c411a4ef6a9ed6d29a02bb7fd08f2ceb0551ecac640"
+}
diff --git a/backend/.sqlx/query-31e486e3377e79bfab4e391d6789d081edf45fef34f630372815ec323544acee.json b/backend/.sqlx/query-31e486e3377e79bfab4e391d6789d081edf45fef34f630372815ec323544acee.json
new file mode 100644
index 0000000000..9c71de3e01
--- /dev/null
+++ b/backend/.sqlx/query-31e486e3377e79bfab4e391d6789d081edf45fef34f630372815ec323544acee.json
@@ -0,0 +1,14 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms, summary, created_by)\n VALUES ('test-workspace', 'folder1', 'folder1', ARRAY['u/test-user-2']::varchar[], $1, '', 'test-user-2')",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": [
+ "Jsonb"
+ ]
+ },
+ "nullable": []
+ },
+ "hash": "31e486e3377e79bfab4e391d6789d081edf45fef34f630372815ec323544acee"
+}
diff --git a/backend/.sqlx/query-2a49e5b5486b650d96f3e9038cba8a5f2e75d3b12ee4718452e82c7318b1bcf4.json b/backend/.sqlx/query-3c42a56d0ffe39ad217f2ee603431637bcb22c6e713a21bdb83204de9cf383d7.json
similarity index 55%
rename from backend/.sqlx/query-2a49e5b5486b650d96f3e9038cba8a5f2e75d3b12ee4718452e82c7318b1bcf4.json
rename to backend/.sqlx/query-3c42a56d0ffe39ad217f2ee603431637bcb22c6e713a21bdb83204de9cf383d7.json
index 77f61ccc47..83a2c7bfc6 100644
--- a/backend/.sqlx/query-2a49e5b5486b650d96f3e9038cba8a5f2e75d3b12ee4718452e82c7318b1bcf4.json
+++ b/backend/.sqlx/query-3c42a56d0ffe39ad217f2ee603431637bcb22c6e713a21bdb83204de9cf383d7.json
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
- "query": "SELECT EXISTS(SELECT 1 FROM script WHERE path = $1 AND workspace_id = $2 ORDER BY created_at DESC LIMIT 1)",
+ "query": "SELECT EXISTS(SELECT 1 FROM workspace_settings WHERE workspace_id = $1)",
"describe": {
"columns": [
{
@@ -11,7 +11,6 @@
],
"parameters": {
"Left": [
- "Text",
"Text"
]
},
@@ -19,5 +18,5 @@
null
]
},
- "hash": "2a49e5b5486b650d96f3e9038cba8a5f2e75d3b12ee4718452e82c7318b1bcf4"
+ "hash": "3c42a56d0ffe39ad217f2ee603431637bcb22c6e713a21bdb83204de9cf383d7"
}
diff --git a/backend/.sqlx/query-dbc7e74e259b502e700491ee0248e0c9c8c61e1bf609be60ac5dc5d438189353.json b/backend/.sqlx/query-3e68b73a1c2fdcb2ef538de1addbb096921e26fed398d5c0966b60503ca97fdb.json
similarity index 68%
rename from backend/.sqlx/query-dbc7e74e259b502e700491ee0248e0c9c8c61e1bf609be60ac5dc5d438189353.json
rename to backend/.sqlx/query-3e68b73a1c2fdcb2ef538de1addbb096921e26fed398d5c0966b60503ca97fdb.json
index 5791090fc1..a40cc12148 100644
--- a/backend/.sqlx/query-dbc7e74e259b502e700491ee0248e0c9c8c61e1bf609be60ac5dc5d438189353.json
+++ b/backend/.sqlx/query-3e68b73a1c2fdcb2ef538de1addbb096921e26fed398d5c0966b60503ca97fdb.json
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
- "query": "\n WITH job_info AS (\n SELECT id, kind::text AS kind, parent_job\n FROM v2_job\n WHERE id = $1\n )\n SELECT\n q.id AS \"id!\",\n s.flow_status,\n q.suspend AS \"suspend!\",\n j.runnable_path AS script_path,\n j.permissioned_as_email AS email,\n (ji.kind IN ('flow', 'flowpreview')) AS \"is_flow_level!\",\n (ji.kind NOT IN ('flow', 'flowpreview') AND q.id = ji.id) AS \"is_wac!\"\n FROM job_info ji\n JOIN v2_job_queue q ON q.id = CASE\n WHEN ji.kind IN ('flow', 'flowpreview') THEN ji.id\n ELSE COALESCE(ji.parent_job, ji.id)\n END\n JOIN v2_job j ON j.id = q.id\n LEFT JOIN v2_job_status s ON s.id = q.id\n FOR UPDATE OF q\n ",
+ "query": "\n WITH job_info AS (\n SELECT id, kind::text AS kind, parent_job\n FROM v2_job\n WHERE id = $1\n )\n SELECT\n q.id AS \"id!\",\n s.flow_status,\n q.suspend AS \"suspend!\",\n j.runnable_path AS script_path,\n j.permissioned_as_email AS email,\n (ji.kind IN ('flow', 'flowpreview', 'singlestepflow')) AS \"is_flow_level!\",\n (ji.kind NOT IN ('flow', 'flowpreview', 'singlestepflow') AND q.id = ji.id) AS \"is_wac!\"\n FROM job_info ji\n JOIN v2_job_queue q ON q.id = CASE\n WHEN ji.kind IN ('flow', 'flowpreview', 'singlestepflow') THEN ji.id\n ELSE COALESCE(ji.parent_job, ji.id)\n END\n JOIN v2_job j ON j.id = q.id\n LEFT JOIN v2_job_status s ON s.id = q.id\n FOR UPDATE OF q\n ",
"describe": {
"columns": [
{
@@ -54,5 +54,5 @@
null
]
},
- "hash": "dbc7e74e259b502e700491ee0248e0c9c8c61e1bf609be60ac5dc5d438189353"
+ "hash": "3e68b73a1c2fdcb2ef538de1addbb096921e26fed398d5c0966b60503ca97fdb"
}
diff --git a/backend/.sqlx/query-3fa3d1fa1add8e187fcbaf7351b721ad0f3e2888af207e8830ccf5e921c5fd60.json b/backend/.sqlx/query-3fa3d1fa1add8e187fcbaf7351b721ad0f3e2888af207e8830ccf5e921c5fd60.json
deleted file mode 100644
index 3bb4151982..0000000000
--- a/backend/.sqlx/query-3fa3d1fa1add8e187fcbaf7351b721ad0f3e2888af207e8830ccf5e921c5fd60.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "db_name": "PostgreSQL",
- "query": "INSERT INTO concurrency_counter(concurrency_id, job_uuids) \n VALUES ($1, $2)\n ON CONFLICT (concurrency_id)\n DO UPDATE SET job_uuids = jsonb_set(concurrency_counter.job_uuids, array[$3], '{}')",
- "describe": {
- "columns": [],
- "parameters": {
- "Left": [
- "Varchar",
- "Jsonb",
- "Text"
- ]
- },
- "nullable": []
- },
- "hash": "3fa3d1fa1add8e187fcbaf7351b721ad0f3e2888af207e8830ccf5e921c5fd60"
-}
diff --git a/backend/.sqlx/query-3fac8694f59803a42b635ce7dd1e60a7a4f53c3b7ed6592f70ef4fed97b2bc6d.json b/backend/.sqlx/query-3fac8694f59803a42b635ce7dd1e60a7a4f53c3b7ed6592f70ef4fed97b2bc6d.json
new file mode 100644
index 0000000000..be9274cf37
--- /dev/null
+++ b/backend/.sqlx/query-3fac8694f59803a42b635ce7dd1e60a7a4f53c3b7ed6592f70ef4fed97b2bc6d.json
@@ -0,0 +1,20 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "SELECT COUNT(*) AS \"count!\" FROM workspace_diff\n WHERE source_workspace_id = 'test-workspace'\n AND fork_workspace_id = 'wm-fork-rename-test'\n AND kind = 'script'\n AND path = 'f/folder2/myscript'",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "count!",
+ "type_info": "Int8"
+ }
+ ],
+ "parameters": {
+ "Left": []
+ },
+ "nullable": [
+ null
+ ]
+ },
+ "hash": "3fac8694f59803a42b635ce7dd1e60a7a4f53c3b7ed6592f70ef4fed97b2bc6d"
+}
diff --git a/backend/.sqlx/query-447fb87252e831db13199b311a3aa0100b2a996115108316bbb2f5a16b270ea6.json b/backend/.sqlx/query-447fb87252e831db13199b311a3aa0100b2a996115108316bbb2f5a16b270ea6.json
new file mode 100644
index 0000000000..dc71dd37bd
--- /dev/null
+++ b/backend/.sqlx/query-447fb87252e831db13199b311a3aa0100b2a996115108316bbb2f5a16b270ea6.json
@@ -0,0 +1,16 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "INSERT INTO concurrency_key (job_id, key, ended_at) VALUES ($1, $2, $3)",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": [
+ "Uuid",
+ "Varchar",
+ "Timestamptz"
+ ]
+ },
+ "nullable": []
+ },
+ "hash": "447fb87252e831db13199b311a3aa0100b2a996115108316bbb2f5a16b270ea6"
+}
diff --git a/backend/.sqlx/query-44dc4a9fe7b51a184ed4599be8342d730a45b431b057cee66688256e455b62e3.json b/backend/.sqlx/query-44dc4a9fe7b51a184ed4599be8342d730a45b431b057cee66688256e455b62e3.json
new file mode 100644
index 0000000000..0aa56e6a3a
--- /dev/null
+++ b/backend/.sqlx/query-44dc4a9fe7b51a184ed4599be8342d730a45b431b057cee66688256e455b62e3.json
@@ -0,0 +1,15 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "DELETE FROM variable WHERE workspace_id = $1 AND path = ANY($2)",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": [
+ "Text",
+ "TextArray"
+ ]
+ },
+ "nullable": []
+ },
+ "hash": "44dc4a9fe7b51a184ed4599be8342d730a45b431b057cee66688256e455b62e3"
+}
diff --git a/backend/.sqlx/query-4a43d4df6c5b2e8dda4308dcb88c23caf312ec377dd91e5307f00d3fb8ec325d.json b/backend/.sqlx/query-4a43d4df6c5b2e8dda4308dcb88c23caf312ec377dd91e5307f00d3fb8ec325d.json
new file mode 100644
index 0000000000..2a6930755a
--- /dev/null
+++ b/backend/.sqlx/query-4a43d4df6c5b2e8dda4308dcb88c23caf312ec377dd91e5307f00d3fb8ec325d.json
@@ -0,0 +1,99 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "\n SELECT\n id,\n args as \"args: _\",\n created_at,\n kind AS \"kind: _\"\n FROM v2_job\n WHERE workspace_id = $1\n AND (\n kind = 'unassigned_script'::JOB_KIND OR\n kind = 'unassigned_flow'::JOB_KIND OR\n kind = 'unassigned_singlestepflow'::JOB_KIND\n )\n AND trigger_kind = $2\n AND trigger = $3\n AND id = ANY($4)\n ",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "id",
+ "type_info": "Uuid"
+ },
+ {
+ "ordinal": 1,
+ "name": "args: _",
+ "type_info": "Jsonb"
+ },
+ {
+ "ordinal": 2,
+ "name": "created_at",
+ "type_info": "Timestamptz"
+ },
+ {
+ "ordinal": 3,
+ "name": "kind: _",
+ "type_info": {
+ "Custom": {
+ "name": "job_kind",
+ "kind": {
+ "Enum": [
+ "script",
+ "preview",
+ "flow",
+ "dependencies",
+ "flowpreview",
+ "script_hub",
+ "identity",
+ "flowdependencies",
+ "http",
+ "graphql",
+ "postgresql",
+ "noop",
+ "appdependencies",
+ "deploymentcallback",
+ "singlestepflow",
+ "flowscript",
+ "flownode",
+ "appscript",
+ "aiagent",
+ "unassigned_script",
+ "unassigned_flow",
+ "unassigned_singlestepflow"
+ ]
+ }
+ }
+ }
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Text",
+ {
+ "Custom": {
+ "name": "job_trigger_kind",
+ "kind": {
+ "Enum": [
+ "webhook",
+ "http",
+ "websocket",
+ "kafka",
+ "email",
+ "nats",
+ "schedule",
+ "app",
+ "ui",
+ "postgres",
+ "sqs",
+ "gcp",
+ "mqtt",
+ "nextcloud",
+ "google",
+ "ci_test",
+ "github",
+ "azure"
+ ]
+ }
+ }
+ },
+ "Text",
+ "UuidArray"
+ ]
+ },
+ "nullable": [
+ false,
+ true,
+ false,
+ false
+ ]
+ },
+ "hash": "4a43d4df6c5b2e8dda4308dcb88c23caf312ec377dd91e5307f00d3fb8ec325d"
+}
diff --git a/backend/.sqlx/query-5104cf045dc9b7b82d0028af11cfb5c2f6fd58e518085caf8e8d189951f7c4d8.json b/backend/.sqlx/query-5104cf045dc9b7b82d0028af11cfb5c2f6fd58e518085caf8e8d189951f7c4d8.json
new file mode 100644
index 0000000000..46025de086
--- /dev/null
+++ b/backend/.sqlx/query-5104cf045dc9b7b82d0028af11cfb5c2f6fd58e518085caf8e8d189951f7c4d8.json
@@ -0,0 +1,28 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "INSERT INTO draft\n (workspace_id, path, value, typ)\n VALUES ($1, $2, $3::text::json, $4)\n ON CONFLICT (workspace_id, path, typ)\n DO UPDATE SET value = EXCLUDED.value, created_at = now()",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": [
+ "Varchar",
+ "Varchar",
+ "Text",
+ {
+ "Custom": {
+ "name": "draft_type",
+ "kind": {
+ "Enum": [
+ "script",
+ "flow",
+ "app"
+ ]
+ }
+ }
+ }
+ ]
+ },
+ "nullable": []
+ },
+ "hash": "5104cf045dc9b7b82d0028af11cfb5c2f6fd58e518085caf8e8d189951f7c4d8"
+}
diff --git a/backend/.sqlx/query-52777947d60d5ddd6d28852a1cf104a5af2fd565706204b7d5d33594ff91e61e.json b/backend/.sqlx/query-52777947d60d5ddd6d28852a1cf104a5af2fd565706204b7d5d33594ff91e61e.json
new file mode 100644
index 0000000000..3aba64d16c
--- /dev/null
+++ b/backend/.sqlx/query-52777947d60d5ddd6d28852a1cf104a5af2fd565706204b7d5d33594ff91e61e.json
@@ -0,0 +1,66 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "SELECT label, token_prefix, expiration, created_at, last_used_at, scopes, workspace_id, read_only FROM token WHERE email = $1\n ORDER BY created_at DESC LIMIT $2 OFFSET $3",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "label",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 1,
+ "name": "token_prefix",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 2,
+ "name": "expiration",
+ "type_info": "Timestamptz"
+ },
+ {
+ "ordinal": 3,
+ "name": "created_at",
+ "type_info": "Timestamptz"
+ },
+ {
+ "ordinal": 4,
+ "name": "last_used_at",
+ "type_info": "Timestamptz"
+ },
+ {
+ "ordinal": 5,
+ "name": "scopes",
+ "type_info": "TextArray"
+ },
+ {
+ "ordinal": 6,
+ "name": "workspace_id",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 7,
+ "name": "read_only",
+ "type_info": "Bool"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Text",
+ "Int8",
+ "Int8"
+ ]
+ },
+ "nullable": [
+ true,
+ false,
+ true,
+ false,
+ false,
+ true,
+ true,
+ false
+ ]
+ },
+ "hash": "52777947d60d5ddd6d28852a1cf104a5af2fd565706204b7d5d33594ff91e61e"
+}
diff --git a/backend/.sqlx/query-5347ec9ab6de69a99e8823d2199757b244b280e1262dcbccd2fc5189c7b3d25b.json b/backend/.sqlx/query-5347ec9ab6de69a99e8823d2199757b244b280e1262dcbccd2fc5189c7b3d25b.json
new file mode 100644
index 0000000000..871cbd144e
--- /dev/null
+++ b/backend/.sqlx/query-5347ec9ab6de69a99e8823d2199757b244b280e1262dcbccd2fc5189c7b3d25b.json
@@ -0,0 +1,31 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "SELECT workspace_id, path FROM app WHERE custom_path = $1 AND ($2::TEXT IS NULL OR workspace_id = $2) AND NOT (path = $3 AND workspace_id = $4) LIMIT 1",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "workspace_id",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 1,
+ "name": "path",
+ "type_info": "Varchar"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Text",
+ "Text",
+ "Text",
+ "Text"
+ ]
+ },
+ "nullable": [
+ false,
+ false
+ ]
+ },
+ "hash": "5347ec9ab6de69a99e8823d2199757b244b280e1262dcbccd2fc5189c7b3d25b"
+}
diff --git a/backend/.sqlx/query-5494652553c59b72ca5db4350a8ba3d8bbf1608519b08f2544c64ecfe130c537.json b/backend/.sqlx/query-5494652553c59b72ca5db4350a8ba3d8bbf1608519b08f2544c64ecfe130c537.json
new file mode 100644
index 0000000000..cd81ba0052
--- /dev/null
+++ b/backend/.sqlx/query-5494652553c59b72ca5db4350a8ba3d8bbf1608519b08f2544c64ecfe130c537.json
@@ -0,0 +1,17 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "UPDATE variable SET labels = $1, edited_at = now(), edited_by = $4 WHERE path = $2 AND workspace_id = $3",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": [
+ "TextArray",
+ "Text",
+ "Text",
+ "Varchar"
+ ]
+ },
+ "nullable": []
+ },
+ "hash": "5494652553c59b72ca5db4350a8ba3d8bbf1608519b08f2544c64ecfe130c537"
+}
diff --git a/backend/.sqlx/query-54d3dd91f3348c03b8b39ecb59c85242186449ff592ed457daac59b37b94aa00.json b/backend/.sqlx/query-54d3dd91f3348c03b8b39ecb59c85242186449ff592ed457daac59b37b94aa00.json
new file mode 100644
index 0000000000..962446787e
--- /dev/null
+++ b/backend/.sqlx/query-54d3dd91f3348c03b8b39ecb59c85242186449ff592ed457daac59b37b94aa00.json
@@ -0,0 +1,17 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "INSERT INTO public.flow(workspace_id, summary, description, path, versions, schema, value, edited_by) VALUES ($1, '', '', $2, ARRAY[$3]::bigint[], '{}'::jsonb, $4, 'system')",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": [
+ "Varchar",
+ "Varchar",
+ "Int8",
+ "Jsonb"
+ ]
+ },
+ "nullable": []
+ },
+ "hash": "54d3dd91f3348c03b8b39ecb59c85242186449ff592ed457daac59b37b94aa00"
+}
diff --git a/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json b/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json
index 713ccb9dd3..36ddb8ab9f 100644
--- a/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json
+++ b/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json
@@ -15,7 +15,7 @@
]
},
"nullable": [
- null
+ true
]
},
"hash": "5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55"
diff --git a/backend/.sqlx/query-5a96c213dbfb0106fe28c9c397ffdb5caf75062248e2f29cd9e4ac3180ee4b31.json b/backend/.sqlx/query-5a96c213dbfb0106fe28c9c397ffdb5caf75062248e2f29cd9e4ac3180ee4b31.json
new file mode 100644
index 0000000000..c2a617f3c8
--- /dev/null
+++ b/backend/.sqlx/query-5a96c213dbfb0106fe28c9c397ffdb5caf75062248e2f29cd9e4ac3180ee4b31.json
@@ -0,0 +1,12 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "INSERT INTO usr(workspace_id, email, username, is_admin, role)\n VALUES ('wm-fork-test-workspace', 'test@windmill.dev', 'test-user', true, 'Admin')",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": []
+ },
+ "nullable": []
+ },
+ "hash": "5a96c213dbfb0106fe28c9c397ffdb5caf75062248e2f29cd9e4ac3180ee4b31"
+}
diff --git a/backend/.sqlx/query-5c705ea49ceda8b281155e38ebdb91e3896009a047286747a9c9f4e8e0192e4a.json b/backend/.sqlx/query-5c705ea49ceda8b281155e38ebdb91e3896009a047286747a9c9f4e8e0192e4a.json
new file mode 100644
index 0000000000..85077b883b
--- /dev/null
+++ b/backend/.sqlx/query-5c705ea49ceda8b281155e38ebdb91e3896009a047286747a9c9f4e8e0192e4a.json
@@ -0,0 +1,15 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "INSERT INTO ws_specific (workspace_id, item_kind, path) VALUES ($1, 'variable', $2) ON CONFLICT DO NOTHING",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": [
+ "Varchar",
+ "Varchar"
+ ]
+ },
+ "nullable": []
+ },
+ "hash": "5c705ea49ceda8b281155e38ebdb91e3896009a047286747a9c9f4e8e0192e4a"
+}
diff --git a/backend/.sqlx/query-6474dbc070afdce3e6bc74b02f93d94c27fcbbf989a4a70678dbc54e0d896e24.json b/backend/.sqlx/query-6474dbc070afdce3e6bc74b02f93d94c27fcbbf989a4a70678dbc54e0d896e24.json
new file mode 100644
index 0000000000..702e6e36bb
--- /dev/null
+++ b/backend/.sqlx/query-6474dbc070afdce3e6bc74b02f93d94c27fcbbf989a4a70678dbc54e0d896e24.json
@@ -0,0 +1,41 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "\n SELECT service_config, webhook_token_hash\n FROM native_trigger\n WHERE workspace_id = $1\n AND service_name = $2\n AND external_id = $3\n FOR UPDATE SKIP LOCKED\n ",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "service_config",
+ "type_info": "Jsonb"
+ },
+ {
+ "ordinal": 1,
+ "name": "webhook_token_hash",
+ "type_info": "Varchar"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Text",
+ {
+ "Custom": {
+ "name": "native_trigger_service",
+ "kind": {
+ "Enum": [
+ "nextcloud",
+ "google",
+ "github"
+ ]
+ }
+ }
+ },
+ "Text"
+ ]
+ },
+ "nullable": [
+ true,
+ false
+ ]
+ },
+ "hash": "6474dbc070afdce3e6bc74b02f93d94c27fcbbf989a4a70678dbc54e0d896e24"
+}
diff --git a/backend/.sqlx/query-652637b534f7d7b4c429a201247821e9f568b1976ea462a09e779e9a4c490197.json b/backend/.sqlx/query-652637b534f7d7b4c429a201247821e9f568b1976ea462a09e779e9a4c490197.json
new file mode 100644
index 0000000000..7420f30c6e
--- /dev/null
+++ b/backend/.sqlx/query-652637b534f7d7b4c429a201247821e9f568b1976ea462a09e779e9a4c490197.json
@@ -0,0 +1,12 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "INSERT INTO usr (workspace_id, email, username, is_admin, role) VALUES\n ('wm-fork-visibility-test', 'test2@windmill.dev', 'test-user-2', false, 'User')",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": []
+ },
+ "nullable": []
+ },
+ "hash": "652637b534f7d7b4c429a201247821e9f568b1976ea462a09e779e9a4c490197"
+}
diff --git a/backend/.sqlx/query-406bcbf55758b10243c8eaff1c349b8082c0052d626bf67e08317e56ab9ad026.json b/backend/.sqlx/query-676405f5ba49c1c711646bd0882fb888d53c5e4aa53bfc43ca296863bc61813f.json
similarity index 62%
rename from backend/.sqlx/query-406bcbf55758b10243c8eaff1c349b8082c0052d626bf67e08317e56ab9ad026.json
rename to backend/.sqlx/query-676405f5ba49c1c711646bd0882fb888d53c5e4aa53bfc43ca296863bc61813f.json
index 2b5b68dfae..a1aa4cc015 100644
--- a/backend/.sqlx/query-406bcbf55758b10243c8eaff1c349b8082c0052d626bf67e08317e56ab9ad026.json
+++ b/backend/.sqlx/query-676405f5ba49c1c711646bd0882fb888d53c5e4aa53bfc43ca296863bc61813f.json
@@ -1,42 +1,32 @@
{
"db_name": "PostgreSQL",
- "query": "SELECT label, email, scopes, workspace_id, super_admin, owner, expiration FROM token WHERE token_hash = $1",
+ "query": "SELECT email, scopes, workspace_id, super_admin, owner FROM token WHERE token_hash = $1",
"describe": {
"columns": [
{
"ordinal": 0,
- "name": "label",
- "type_info": "Varchar"
- },
- {
- "ordinal": 1,
"name": "email",
"type_info": "Varchar"
},
{
- "ordinal": 2,
+ "ordinal": 1,
"name": "scopes",
"type_info": "TextArray"
},
{
- "ordinal": 3,
+ "ordinal": 2,
"name": "workspace_id",
"type_info": "Varchar"
},
{
- "ordinal": 4,
+ "ordinal": 3,
"name": "super_admin",
"type_info": "Bool"
},
{
- "ordinal": 5,
+ "ordinal": 4,
"name": "owner",
"type_info": "Varchar"
- },
- {
- "ordinal": 6,
- "name": "expiration",
- "type_info": "Timestamptz"
}
],
"parameters": {
@@ -48,11 +38,9 @@
true,
true,
true,
- true,
false,
- true,
true
]
},
- "hash": "406bcbf55758b10243c8eaff1c349b8082c0052d626bf67e08317e56ab9ad026"
+ "hash": "676405f5ba49c1c711646bd0882fb888d53c5e4aa53bfc43ca296863bc61813f"
}
diff --git a/backend/.sqlx/query-69378789e2d14778812c8d6f1a6cb2cb3345869afea8a82b8416f762aacf5dd9.json b/backend/.sqlx/query-69378789e2d14778812c8d6f1a6cb2cb3345869afea8a82b8416f762aacf5dd9.json
new file mode 100644
index 0000000000..dcb44dbb90
--- /dev/null
+++ b/backend/.sqlx/query-69378789e2d14778812c8d6f1a6cb2cb3345869afea8a82b8416f762aacf5dd9.json
@@ -0,0 +1,15 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "DELETE FROM ws_specific WHERE workspace_id = $1 AND item_kind = 'variable' AND path = ANY($2)",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": [
+ "Text",
+ "TextArray"
+ ]
+ },
+ "nullable": []
+ },
+ "hash": "69378789e2d14778812c8d6f1a6cb2cb3345869afea8a82b8416f762aacf5dd9"
+}
diff --git a/backend/.sqlx/query-6a7e0a5ef270d7effeccbb8f9edba6adcb5170d2825644fb02f6a1a57447f4fb.json b/backend/.sqlx/query-6a7e0a5ef270d7effeccbb8f9edba6adcb5170d2825644fb02f6a1a57447f4fb.json
new file mode 100644
index 0000000000..9c35d02b73
--- /dev/null
+++ b/backend/.sqlx/query-6a7e0a5ef270d7effeccbb8f9edba6adcb5170d2825644fb02f6a1a57447f4fb.json
@@ -0,0 +1,15 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "DELETE FROM ws_specific WHERE workspace_id = $1 AND item_kind = 'variable' AND path = $2",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": [
+ "Text",
+ "Text"
+ ]
+ },
+ "nullable": []
+ },
+ "hash": "6a7e0a5ef270d7effeccbb8f9edba6adcb5170d2825644fb02f6a1a57447f4fb"
+}
diff --git a/backend/.sqlx/query-6cda14bc33e3144b78b2147ac1153c516b5962b5648ae07c247fbd57b1b16ada.json b/backend/.sqlx/query-6cda14bc33e3144b78b2147ac1153c516b5962b5648ae07c247fbd57b1b16ada.json
new file mode 100644
index 0000000000..e8f746ded0
--- /dev/null
+++ b/backend/.sqlx/query-6cda14bc33e3144b78b2147ac1153c516b5962b5648ae07c247fbd57b1b16ada.json
@@ -0,0 +1,89 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "WITH norm AS (\n SELECT\n j.id, j.workspace_id, j.runnable_path, j.runnable_id, j.kind, j.args,\n -- Project effective kind for dispatch: pass script/flow through;\n -- for singlestepflow, read the wrapped runnable's type from\n -- raw_flow.modules[id='a'].value.type (always 'script' or 'flow').\n CASE\n WHEN j.kind IN ('script', 'flow') THEN j.kind::text\n WHEN j.kind = 'singlestepflow' THEN\n COALESCE(\n (SELECT m->'value'->>'type'\n FROM jsonb_array_elements(j.raw_flow->'modules') m\n WHERE m->>'id' = 'a'\n LIMIT 1),\n 'script'\n )\n END AS norm_kind,\n -- Pinned script hash for script-wrapped singlestepflow lives in\n -- raw_flow.modules[id='a'].value.hash. Flow-wrapped doesn't pin a\n -- version, so this is NULL there (Flow rerun pushes by path).\n (CASE WHEN j.kind = 'singlestepflow' THEN\n (SELECT ('x' || lpad(m->'value'->>'hash', 16, '0'))::bit(64)::bigint\n FROM jsonb_array_elements(j.raw_flow->'modules') m\n WHERE m->>'id' = 'a'\n AND m->'value'->>'hash' IS NOT NULL\n LIMIT 1)\n END) AS ssf_hash\n FROM v2_job j\n WHERE j.id = ANY($1)\n AND j.workspace_id = $2\n AND j.kind IN ('script', 'flow', 'singlestepflow')\n )\n SELECT\n n.id,\n n.norm_kind::JOB_KIND AS \"kind!: _\",\n COALESCE(s.path, f.path, n.runnable_path) AS \"script_path!\",\n -- script_hash is unused on the Flow rerun path (path-based push), so\n -- 0 is a safe placeholder when no version is pinned.\n COALESCE(s.hash, f.id, n.ssf_hash, 0::bigint) AS \"script_hash!: _\",\n COALESCE(jc.started_at, jq.scheduled_for, make_date(1970, 1, 1)) AS \"scheduled_for!: _\",\n n.args AS input,\n -- Pinned schema for script/flow; latest-by-path fallback for\n -- singlestepflow so input_transforms still resolve at rerun time.\n COALESCE(\n s.schema,\n f.schema,\n (CASE WHEN n.kind = 'singlestepflow' AND n.norm_kind = 'script' THEN\n (SELECT s2.schema FROM script s2\n WHERE s2.workspace_id = $2 AND s2.path = n.runnable_path\n ORDER BY s2.created_at DESC LIMIT 1)\n END),\n (CASE WHEN n.kind = 'singlestepflow' AND n.norm_kind = 'flow' THEN\n (SELECT fv.schema FROM flow\n LEFT JOIN flow_version fv ON fv.id = flow.versions[array_upper(flow.versions, 1)]\n WHERE flow.workspace_id = $2 AND flow.path = n.runnable_path)\n END)\n ) AS \"schema: _\"\n FROM norm n\n LEFT JOIN script s ON s.hash = n.runnable_id AND n.kind = 'script'\n LEFT JOIN flow_version f ON f.id = n.runnable_id AND f.path = n.runnable_path AND n.kind = 'flow'\n LEFT JOIN v2_job_completed jc ON jc.id = n.id\n LEFT JOIN v2_job_queue jq ON jq.id = n.id\n WHERE n.norm_kind IS NOT NULL\n AND COALESCE(s.path, f.path, n.runnable_path) IS NOT NULL\n AND (\n n.kind = 'singlestepflow'\n OR (COALESCE(s.hash, f.id) IS NOT NULL AND COALESCE(s.path, f.path) IS NOT NULL)\n )",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "id",
+ "type_info": "Uuid"
+ },
+ {
+ "ordinal": 1,
+ "name": "kind!: _",
+ "type_info": {
+ "Custom": {
+ "name": "job_kind",
+ "kind": {
+ "Enum": [
+ "script",
+ "preview",
+ "flow",
+ "dependencies",
+ "flowpreview",
+ "script_hub",
+ "identity",
+ "flowdependencies",
+ "http",
+ "graphql",
+ "postgresql",
+ "noop",
+ "appdependencies",
+ "deploymentcallback",
+ "singlestepflow",
+ "flowscript",
+ "flownode",
+ "appscript",
+ "aiagent",
+ "unassigned_script",
+ "unassigned_flow",
+ "unassigned_singlestepflow"
+ ]
+ }
+ }
+ }
+ },
+ {
+ "ordinal": 2,
+ "name": "script_path!",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 3,
+ "name": "script_hash!: _",
+ "type_info": "Int8"
+ },
+ {
+ "ordinal": 4,
+ "name": "scheduled_for!: _",
+ "type_info": "Timestamptz"
+ },
+ {
+ "ordinal": 5,
+ "name": "input",
+ "type_info": "Jsonb"
+ },
+ {
+ "ordinal": 6,
+ "name": "schema: _",
+ "type_info": "Json"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "UuidArray",
+ "Text"
+ ]
+ },
+ "nullable": [
+ false,
+ null,
+ null,
+ null,
+ null,
+ true,
+ null
+ ]
+ },
+ "hash": "6cda14bc33e3144b78b2147ac1153c516b5962b5648ae07c247fbd57b1b16ada"
+}
diff --git a/backend/.sqlx/query-6d144520b894d21e4cede9370393926fbb892cbe730112c645f11455a2702de1.json b/backend/.sqlx/query-6d144520b894d21e4cede9370393926fbb892cbe730112c645f11455a2702de1.json
new file mode 100644
index 0000000000..379c026f5a
--- /dev/null
+++ b/backend/.sqlx/query-6d144520b894d21e4cede9370393926fbb892cbe730112c645f11455a2702de1.json
@@ -0,0 +1,12 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "INSERT INTO workspace_key(workspace_id, kind, key)\n VALUES ('wm-fork-test-workspace', 'cloud', 'test-key')",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": []
+ },
+ "nullable": []
+ },
+ "hash": "6d144520b894d21e4cede9370393926fbb892cbe730112c645f11455a2702de1"
+}
diff --git a/backend/.sqlx/query-6fcdb09cdd7fedd7e54fdc0e49203f453fc1b85272fe212c0e1bf0ebd28bf58a.json b/backend/.sqlx/query-6fcdb09cdd7fedd7e54fdc0e49203f453fc1b85272fe212c0e1bf0ebd28bf58a.json
new file mode 100644
index 0000000000..17fbb0d5bb
--- /dev/null
+++ b/backend/.sqlx/query-6fcdb09cdd7fedd7e54fdc0e49203f453fc1b85272fe212c0e1bf0ebd28bf58a.json
@@ -0,0 +1,38 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "\n WITH capped AS (\n SELECT timestamp, operation, resource, parameters\n FROM audit_partitioned\n WHERE workspace_id = 'admins'\n AND operation = 'workspace_fairness.capped'\n UNION ALL\n SELECT timestamp, operation, resource, parameters\n FROM audit\n WHERE workspace_id = 'admins'\n AND operation = 'workspace_fairness.capped'\n ORDER BY timestamp DESC\n LIMIT 200\n ), uncapped AS (\n SELECT timestamp, operation, resource, parameters\n FROM audit_partitioned\n WHERE workspace_id = 'admins'\n AND operation = 'workspace_fairness.uncapped'\n UNION ALL\n SELECT timestamp, operation, resource, parameters\n FROM audit\n WHERE workspace_id = 'admins'\n AND operation = 'workspace_fairness.uncapped'\n ORDER BY timestamp DESC\n LIMIT 200\n )\n SELECT timestamp AS \"timestamp!\",\n operation::text AS \"operation!\",\n resource AS workspace_id,\n parameters\n FROM (SELECT * FROM capped UNION ALL SELECT * FROM uncapped) e\n ORDER BY timestamp DESC\n ",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "timestamp!",
+ "type_info": "Timestamptz"
+ },
+ {
+ "ordinal": 1,
+ "name": "operation!",
+ "type_info": "Text"
+ },
+ {
+ "ordinal": 2,
+ "name": "workspace_id",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 3,
+ "name": "parameters",
+ "type_info": "Jsonb"
+ }
+ ],
+ "parameters": {
+ "Left": []
+ },
+ "nullable": [
+ null,
+ null,
+ null,
+ null
+ ]
+ },
+ "hash": "6fcdb09cdd7fedd7e54fdc0e49203f453fc1b85272fe212c0e1bf0ebd28bf58a"
+}
diff --git a/backend/.sqlx/query-757ef6215d3d385cb3a69e26ee4ca846dd5e7fe7ceb1aa8b3fcd26a2bd30eb2c.json b/backend/.sqlx/query-757ef6215d3d385cb3a69e26ee4ca846dd5e7fe7ceb1aa8b3fcd26a2bd30eb2c.json
deleted file mode 100644
index b705130ffe..0000000000
--- a/backend/.sqlx/query-757ef6215d3d385cb3a69e26ee4ca846dd5e7fe7ceb1aa8b3fcd26a2bd30eb2c.json
+++ /dev/null
@@ -1,62 +0,0 @@
-{
- "db_name": "PostgreSQL",
- "query": "\n SELECT\n id,\n args as \"args: _\",\n created_at\n FROM v2_job\n WHERE workspace_id = $1\n AND (\n kind = 'unassigned_script'::JOB_KIND OR\n kind = 'unassigned_flow'::JOB_KIND OR\n kind = 'unassigned_singlestepflow'::JOB_KIND\n )\n AND trigger_kind = $2\n AND trigger = $3\n ",
- "describe": {
- "columns": [
- {
- "ordinal": 0,
- "name": "id",
- "type_info": "Uuid"
- },
- {
- "ordinal": 1,
- "name": "args: _",
- "type_info": "Jsonb"
- },
- {
- "ordinal": 2,
- "name": "created_at",
- "type_info": "Timestamptz"
- }
- ],
- "parameters": {
- "Left": [
- "Text",
- {
- "Custom": {
- "name": "job_trigger_kind",
- "kind": {
- "Enum": [
- "webhook",
- "http",
- "websocket",
- "kafka",
- "email",
- "nats",
- "schedule",
- "app",
- "ui",
- "postgres",
- "sqs",
- "gcp",
- "mqtt",
- "nextcloud",
- "google",
- "ci_test",
- "github",
- "azure"
- ]
- }
- }
- },
- "Text"
- ]
- },
- "nullable": [
- false,
- true,
- false
- ]
- },
- "hash": "757ef6215d3d385cb3a69e26ee4ca846dd5e7fe7ceb1aa8b3fcd26a2bd30eb2c"
-}
diff --git a/backend/.sqlx/query-77d8a6d1b6d6cd8def844981a2bf87c66c266526a9fc484aa7f58cbeea6672fd.json b/backend/.sqlx/query-77d8a6d1b6d6cd8def844981a2bf87c66c266526a9fc484aa7f58cbeea6672fd.json
new file mode 100644
index 0000000000..958ce55f40
--- /dev/null
+++ b/backend/.sqlx/query-77d8a6d1b6d6cd8def844981a2bf87c66c266526a9fc484aa7f58cbeea6672fd.json
@@ -0,0 +1,20 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "SELECT txid_snapshot_xmin(txid_current_snapshot())::bigint AS \"x!\"",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "x!",
+ "type_info": "Int8"
+ }
+ ],
+ "parameters": {
+ "Left": []
+ },
+ "nullable": [
+ null
+ ]
+ },
+ "hash": "77d8a6d1b6d6cd8def844981a2bf87c66c266526a9fc484aa7f58cbeea6672fd"
+}
diff --git a/backend/.sqlx/query-79e56c3cf21dbc57193358ec79393fc669d73448552f68f83caf32cfb0bf4be0.json b/backend/.sqlx/query-79e56c3cf21dbc57193358ec79393fc669d73448552f68f83caf32cfb0bf4be0.json
new file mode 100644
index 0000000000..b07fe6faf7
--- /dev/null
+++ b/backend/.sqlx/query-79e56c3cf21dbc57193358ec79393fc669d73448552f68f83caf32cfb0bf4be0.json
@@ -0,0 +1,14 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "INSERT INTO concurrency_counter(concurrency_id, job_uuids) VALUES ($1, '{}'::jsonb) ON CONFLICT DO NOTHING",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": [
+ "Varchar"
+ ]
+ },
+ "nullable": []
+ },
+ "hash": "79e56c3cf21dbc57193358ec79393fc669d73448552f68f83caf32cfb0bf4be0"
+}
diff --git a/backend/.sqlx/query-7e11b36055ed15a8f3a68511bc15b52aaf1f79874b3369e7a01171491f6d9083.json b/backend/.sqlx/query-7e11b36055ed15a8f3a68511bc15b52aaf1f79874b3369e7a01171491f6d9083.json
new file mode 100644
index 0000000000..253dde7a8e
--- /dev/null
+++ b/backend/.sqlx/query-7e11b36055ed15a8f3a68511bc15b52aaf1f79874b3369e7a01171491f6d9083.json
@@ -0,0 +1,15 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "DELETE FROM ws_specific\n WHERE workspace_id = $1 AND item_kind = 'variable' AND path = ANY($2)",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": [
+ "Text",
+ "TextArray"
+ ]
+ },
+ "nullable": []
+ },
+ "hash": "7e11b36055ed15a8f3a68511bc15b52aaf1f79874b3369e7a01171491f6d9083"
+}
diff --git a/backend/.sqlx/query-7f832370916794ab0e5645053688c24678f1519d49ee7263a86dba71d45b8e8c.json b/backend/.sqlx/query-7f832370916794ab0e5645053688c24678f1519d49ee7263a86dba71d45b8e8c.json
new file mode 100644
index 0000000000..173d8f2281
--- /dev/null
+++ b/backend/.sqlx/query-7f832370916794ab0e5645053688c24678f1519d49ee7263a86dba71d45b8e8c.json
@@ -0,0 +1,22 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "INSERT INTO token\n (token_hash, token_prefix, token, email, label, expiration, super_admin, scopes, read_only)\n VALUES ($1, $2, $3, $4, $5, now() + ($6 || ' seconds')::interval, $7, $8, $9)",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": [
+ "Varchar",
+ "Varchar",
+ "Varchar",
+ "Varchar",
+ "Varchar",
+ "Text",
+ "Bool",
+ "TextArray",
+ "Bool"
+ ]
+ },
+ "nullable": []
+ },
+ "hash": "7f832370916794ab0e5645053688c24678f1519d49ee7263a86dba71d45b8e8c"
+}
diff --git a/backend/.sqlx/query-815d96aea4681490582b08630a30a168cc1191acaab96bed6a016c437059c2cd.json b/backend/.sqlx/query-815d96aea4681490582b08630a30a168cc1191acaab96bed6a016c437059c2cd.json
new file mode 100644
index 0000000000..28a725277d
--- /dev/null
+++ b/backend/.sqlx/query-815d96aea4681490582b08630a30a168cc1191acaab96bed6a016c437059c2cd.json
@@ -0,0 +1,26 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "\n SELECT ws.workspace_id AS \"workspace_id!\", entry->'catalog'->>'resource_path' AS dbname\n FROM workspace_settings ws\n CROSS JOIN LATERAL jsonb_each(\n CASE WHEN jsonb_typeof(ws.ducklake->'ducklakes') = 'object'\n THEN ws.ducklake->'ducklakes'\n ELSE '{}'::jsonb END\n ) AS dl(k, entry)\n WHERE entry->'catalog'->>'resource_type' = 'instance'\n AND entry->'catalog'->>'resource_path' IS NOT NULL\n UNION ALL\n SELECT ws.workspace_id AS \"workspace_id!\", entry->'database'->>'resource_path' AS dbname\n FROM workspace_settings ws\n CROSS JOIN LATERAL jsonb_each(\n CASE WHEN jsonb_typeof(ws.datatable->'datatables') = 'object'\n THEN ws.datatable->'datatables'\n ELSE '{}'::jsonb END\n ) AS dt(k, entry)\n WHERE entry->'database'->>'resource_type' = 'instance'\n AND entry->'database'->>'resource_path' IS NOT NULL\n ",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "workspace_id!",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 1,
+ "name": "dbname",
+ "type_info": "Text"
+ }
+ ],
+ "parameters": {
+ "Left": []
+ },
+ "nullable": [
+ null,
+ null
+ ]
+ },
+ "hash": "815d96aea4681490582b08630a30a168cc1191acaab96bed6a016c437059c2cd"
+}
diff --git a/backend/.sqlx/query-8192986cd6106ed060b3d68dbc21e5bb34f5e68a5c6ac455a9d423188af77b23.json b/backend/.sqlx/query-8192986cd6106ed060b3d68dbc21e5bb34f5e68a5c6ac455a9d423188af77b23.json
new file mode 100644
index 0000000000..7a0c5578b0
--- /dev/null
+++ b/backend/.sqlx/query-8192986cd6106ed060b3d68dbc21e5bb34f5e68a5c6ac455a9d423188af77b23.json
@@ -0,0 +1,15 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "INSERT INTO background_task_state (name, value)\n SELECT $1, jsonb_build_object(\n 'last_xmin', txid_snapshot_xmin(txid_current_snapshot())::bigint,\n 'last_ts', '1970-01-01T00:00:00+00:00')\n WHERE NOT EXISTS (SELECT 1 FROM global_settings WHERE name = $2)\n ON CONFLICT (name) DO NOTHING",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": [
+ "Text",
+ "Text"
+ ]
+ },
+ "nullable": []
+ },
+ "hash": "8192986cd6106ed060b3d68dbc21e5bb34f5e68a5c6ac455a9d423188af77b23"
+}
diff --git a/backend/.sqlx/query-8597cd40f80e69edbf1bc7d7402baca32e33e871be454acb5175c11361fe1b0a.json b/backend/.sqlx/query-8597cd40f80e69edbf1bc7d7402baca32e33e871be454acb5175c11361fe1b0a.json
new file mode 100644
index 0000000000..fd82867507
--- /dev/null
+++ b/backend/.sqlx/query-8597cd40f80e69edbf1bc7d7402baca32e33e871be454acb5175c11361fe1b0a.json
@@ -0,0 +1,14 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "DELETE FROM background_task_state\n WHERE name LIKE $1\n AND updated_at < NOW() - INTERVAL '7 days'",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": [
+ "Text"
+ ]
+ },
+ "nullable": []
+ },
+ "hash": "8597cd40f80e69edbf1bc7d7402baca32e33e871be454acb5175c11361fe1b0a"
+}
diff --git a/backend/.sqlx/query-864f73293d110474bd5544afc92f9060c7677542b0ce3240df214935dccbf509.json b/backend/.sqlx/query-864f73293d110474bd5544afc92f9060c7677542b0ce3240df214935dccbf509.json
new file mode 100644
index 0000000000..c53aac2abb
--- /dev/null
+++ b/backend/.sqlx/query-864f73293d110474bd5544afc92f9060c7677542b0ce3240df214935dccbf509.json
@@ -0,0 +1,16 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "INSERT INTO concurrency_counter(concurrency_id, job_uuids)\n VALUES ($1, $2)\n ON CONFLICT (concurrency_id)\n DO UPDATE SET job_uuids = jsonb_set(concurrency_counter.job_uuids, array[$3], '{}')",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": [
+ "Varchar",
+ "Jsonb",
+ "Text"
+ ]
+ },
+ "nullable": []
+ },
+ "hash": "864f73293d110474bd5544afc92f9060c7677542b0ce3240df214935dccbf509"
+}
diff --git a/backend/.sqlx/query-8711bb7861cb3c453519a620057e1530039c09b824065027387bbb667a49fe8d.json b/backend/.sqlx/query-8711bb7861cb3c453519a620057e1530039c09b824065027387bbb667a49fe8d.json
new file mode 100644
index 0000000000..56b887e8b9
--- /dev/null
+++ b/backend/.sqlx/query-8711bb7861cb3c453519a620057e1530039c09b824065027387bbb667a49fe8d.json
@@ -0,0 +1,16 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "INSERT INTO background_task_state\n (name, value, running, owner, started_at, finished_at, updated_at)\n VALUES ($1, $2, false, $3, now(), now(), now())\n ON CONFLICT (name) DO UPDATE SET\n value = $2, running = false, owner = $3,\n finished_at = now(), updated_at = now()",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": [
+ "Text",
+ "Jsonb",
+ "Text"
+ ]
+ },
+ "nullable": []
+ },
+ "hash": "8711bb7861cb3c453519a620057e1530039c09b824065027387bbb667a49fe8d"
+}
diff --git a/backend/.sqlx/query-8816bbe1ea9ea4f359e7a95857d349fa8bd7d23e4589b6936e0d000745cb3f34.json b/backend/.sqlx/query-8816bbe1ea9ea4f359e7a95857d349fa8bd7d23e4589b6936e0d000745cb3f34.json
new file mode 100644
index 0000000000..53fe03ce5f
--- /dev/null
+++ b/backend/.sqlx/query-8816bbe1ea9ea4f359e7a95857d349fa8bd7d23e4589b6936e0d000745cb3f34.json
@@ -0,0 +1,23 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "SELECT a.path FROM app_script s JOIN app a ON a.id = s.app\n WHERE s.id = $1 AND a.workspace_id = $2",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "path",
+ "type_info": "Varchar"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Int8",
+ "Text"
+ ]
+ },
+ "nullable": [
+ false
+ ]
+ },
+ "hash": "8816bbe1ea9ea4f359e7a95857d349fa8bd7d23e4589b6936e0d000745cb3f34"
+}
diff --git a/backend/.sqlx/query-8aab14a811b7a3f69107530c17564be4bcc58bcd262e8bb0637624a4ba455761.json b/backend/.sqlx/query-8aab14a811b7a3f69107530c17564be4bcc58bcd262e8bb0637624a4ba455761.json
new file mode 100644
index 0000000000..af15115a3c
--- /dev/null
+++ b/backend/.sqlx/query-8aab14a811b7a3f69107530c17564be4bcc58bcd262e8bb0637624a4ba455761.json
@@ -0,0 +1,15 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "INSERT INTO concurrency_counter(concurrency_id, job_uuids) VALUES ($1, $2)",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": [
+ "Varchar",
+ "Jsonb"
+ ]
+ },
+ "nullable": []
+ },
+ "hash": "8aab14a811b7a3f69107530c17564be4bcc58bcd262e8bb0637624a4ba455761"
+}
diff --git a/backend/.sqlx/query-8aae160c589adf02e20b7e6ba860b66fb08f156776b96b7f3701aed330a7d000.json b/backend/.sqlx/query-8aae160c589adf02e20b7e6ba860b66fb08f156776b96b7f3701aed330a7d000.json
new file mode 100644
index 0000000000..d05c5b11af
--- /dev/null
+++ b/backend/.sqlx/query-8aae160c589adf02e20b7e6ba860b66fb08f156776b96b7f3701aed330a7d000.json
@@ -0,0 +1,101 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "WITH active_users AS (SELECT distinct username as email FROM (SELECT username, timestamp, operation FROM audit_partitioned UNION ALL SELECT username, timestamp, operation FROM audit) AS a WHERE timestamp > NOW() - INTERVAL '1 month' AND (operation = 'users.login' OR operation = 'oauth.login' OR operation = 'users.token.refresh')),\n authors as (SELECT distinct email FROM usr WHERE usr.operator IS false)\n SELECT email as \"email!\", (email NOT IN (SELECT email FROM authors)) as operator_only, NULL::bool as is_workspace_admin, login_type::text, verified as \"verified!\", super_admin as \"super_admin!\", devops as \"devops!\", name, company, username, first_time_user as \"first_time_user!\", role_source as \"role_source!\", disabled as \"disabled!\", NULL::text as workspace_id\n FROM password\n WHERE email IN (SELECT email FROM active_users)\n UNION ALL\n SELECT email as \"email!\", operator as operator_only, is_admin as is_workspace_admin, 'service_account'::text as login_type, true as \"verified!\", false as \"super_admin!\", false as \"devops!\", NULL::text as name, NULL::text as company, username, false as \"first_time_user!\", 'service_account'::text as \"role_source!\", disabled as \"disabled!\", workspace_id\n FROM usr\n WHERE is_service_account IS true\n ORDER BY \"super_admin!\" DESC, \"devops!\" DESC\n LIMIT $1 OFFSET $2",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "email!",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 1,
+ "name": "operator_only",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 2,
+ "name": "is_workspace_admin",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 3,
+ "name": "login_type",
+ "type_info": "Text"
+ },
+ {
+ "ordinal": 4,
+ "name": "verified!",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 5,
+ "name": "super_admin!",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 6,
+ "name": "devops!",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 7,
+ "name": "name",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 8,
+ "name": "company",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 9,
+ "name": "username",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 10,
+ "name": "first_time_user!",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 11,
+ "name": "role_source!",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 12,
+ "name": "disabled!",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 13,
+ "name": "workspace_id",
+ "type_info": "Text"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Int8",
+ "Int8"
+ ]
+ },
+ "nullable": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ]
+ },
+ "hash": "8aae160c589adf02e20b7e6ba860b66fb08f156776b96b7f3701aed330a7d000"
+}
diff --git a/backend/.sqlx/query-8d64e61fad7bdf0cc4d4cad1a032e570bdfaf885b4f39ef3ab973256e0448ec7.json b/backend/.sqlx/query-8d64e61fad7bdf0cc4d4cad1a032e570bdfaf885b4f39ef3ab973256e0448ec7.json
new file mode 100644
index 0000000000..1a23b0f877
--- /dev/null
+++ b/backend/.sqlx/query-8d64e61fad7bdf0cc4d4cad1a032e570bdfaf885b4f39ef3ab973256e0448ec7.json
@@ -0,0 +1,35 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "SELECT username, is_admin, operator FROM usr\n WHERE workspace_id = $1 AND email = $2 AND disabled = false",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "username",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 1,
+ "name": "is_admin",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 2,
+ "name": "operator",
+ "type_info": "Bool"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Text",
+ "Text"
+ ]
+ },
+ "nullable": [
+ false,
+ false,
+ false
+ ]
+ },
+ "hash": "8d64e61fad7bdf0cc4d4cad1a032e570bdfaf885b4f39ef3ab973256e0448ec7"
+}
diff --git a/backend/.sqlx/query-8dbe7d38df3493958456bb9454681a20b92779d6c56b116c7189f3055ad15d35.json b/backend/.sqlx/query-8dbe7d38df3493958456bb9454681a20b92779d6c56b116c7189f3055ad15d35.json
new file mode 100644
index 0000000000..4ca7a134e3
--- /dev/null
+++ b/backend/.sqlx/query-8dbe7d38df3493958456bb9454681a20b92779d6c56b116c7189f3055ad15d35.json
@@ -0,0 +1,25 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "SELECT ws AS \"ws!\" FROM list_ws_specific_versions($1, $2, $3, $4)",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "ws!",
+ "type_info": "Varchar"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Text",
+ "Text",
+ "Text",
+ "Text"
+ ]
+ },
+ "nullable": [
+ null
+ ]
+ },
+ "hash": "8dbe7d38df3493958456bb9454681a20b92779d6c56b116c7189f3055ad15d35"
+}
diff --git a/backend/.sqlx/query-8eb5866b6279cb386bbeb7c387a7c731317199b28694a9c3e04028ef1f1d7500.json b/backend/.sqlx/query-8eb5866b6279cb386bbeb7c387a7c731317199b28694a9c3e04028ef1f1d7500.json
new file mode 100644
index 0000000000..0961786e35
--- /dev/null
+++ b/backend/.sqlx/query-8eb5866b6279cb386bbeb7c387a7c731317199b28694a9c3e04028ef1f1d7500.json
@@ -0,0 +1,12 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "DELETE FROM skip_workspace_diff_tally",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": []
+ },
+ "nullable": []
+ },
+ "hash": "8eb5866b6279cb386bbeb7c387a7c731317199b28694a9c3e04028ef1f1d7500"
+}
diff --git a/backend/.sqlx/query-8ed3be54f3ff8a22e1d3e583c1019357829e39659fbc4e5ba8885aef29da6e55.json b/backend/.sqlx/query-8ed3be54f3ff8a22e1d3e583c1019357829e39659fbc4e5ba8885aef29da6e55.json
new file mode 100644
index 0000000000..1420030cd6
--- /dev/null
+++ b/backend/.sqlx/query-8ed3be54f3ff8a22e1d3e583c1019357829e39659fbc4e5ba8885aef29da6e55.json
@@ -0,0 +1,12 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "INSERT INTO workspace (id, name, owner, parent_workspace_id)\n VALUES ('wm-fork-test-workspace', 'Fork', 'test-user', 'test-workspace')",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": []
+ },
+ "nullable": []
+ },
+ "hash": "8ed3be54f3ff8a22e1d3e583c1019357829e39659fbc4e5ba8885aef29da6e55"
+}
diff --git a/backend/.sqlx/query-903c01dbda5996417a81f7fd76fb21a3566667ec1a9463c7464c5cd788d89270.json b/backend/.sqlx/query-903c01dbda5996417a81f7fd76fb21a3566667ec1a9463c7464c5cd788d89270.json
new file mode 100644
index 0000000000..be765afaae
--- /dev/null
+++ b/backend/.sqlx/query-903c01dbda5996417a81f7fd76fb21a3566667ec1a9463c7464c5cd788d89270.json
@@ -0,0 +1,12 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "INSERT INTO workspace_diff\n (source_workspace_id, fork_workspace_id, path, kind, ahead, behind, has_changes)\n VALUES ('test-workspace', 'wm-fork-visibility-test', 'f/folder2/myscript', 'script', 1, 0, NULL)",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": []
+ },
+ "nullable": []
+ },
+ "hash": "903c01dbda5996417a81f7fd76fb21a3566667ec1a9463c7464c5cd788d89270"
+}
diff --git a/backend/.sqlx/query-90c765e384170c2e9f9bc244c7578991315969faf7b845c93678c7ef63b8b8cb.json b/backend/.sqlx/query-90c765e384170c2e9f9bc244c7578991315969faf7b845c93678c7ef63b8b8cb.json
new file mode 100644
index 0000000000..1c560ac187
--- /dev/null
+++ b/backend/.sqlx/query-90c765e384170c2e9f9bc244c7578991315969faf7b845c93678c7ef63b8b8cb.json
@@ -0,0 +1,12 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "DELETE FROM skip_workspace_diff_tally WHERE workspace_id IN ('test-workspace', 'wm-fork-visibility-test')",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": []
+ },
+ "nullable": []
+ },
+ "hash": "90c765e384170c2e9f9bc244c7578991315969faf7b845c93678c7ef63b8b8cb"
+}
diff --git a/backend/.sqlx/query-41f2c271514ee254739c3a097871526adcecdd8729f28c15e0db8cd28eaa8cf0.json b/backend/.sqlx/query-9254e2a0e1be830fa4cab660c69a659e2eb3f3d912e1c062bb8000a2e0a653e8.json
similarity index 61%
rename from backend/.sqlx/query-41f2c271514ee254739c3a097871526adcecdd8729f28c15e0db8cd28eaa8cf0.json
rename to backend/.sqlx/query-9254e2a0e1be830fa4cab660c69a659e2eb3f3d912e1c062bb8000a2e0a653e8.json
index 78efa6ccbc..7959ccdbcb 100644
--- a/backend/.sqlx/query-41f2c271514ee254739c3a097871526adcecdd8729f28c15e0db8cd28eaa8cf0.json
+++ b/backend/.sqlx/query-9254e2a0e1be830fa4cab660c69a659e2eb3f3d912e1c062bb8000a2e0a653e8.json
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
- "query": "SELECT resource.*, (now() > account.expires_at) as is_expired, account.refresh_token != '' as is_refreshed,\n account.refresh_error,\n variable.path IS NOT NULL as is_linked,\n variable.is_oauth as \"is_oauth?\",\n variable.account\n FROM resource\n LEFT JOIN variable ON variable.path = resource.path AND variable.workspace_id = $2\n LEFT JOIN account ON variable.account = account.id AND account.workspace_id = $2\n WHERE resource.path = $1 AND resource.workspace_id = $2",
+ "query": "SELECT resource.workspace_id, resource.path, resource.value, resource.description,\n resource.resource_type, resource.extra_perms, resource.created_by, resource.edited_at,\n resource.labels,\n (now() > account.expires_at) as is_expired, account.refresh_token != '' as is_refreshed,\n account.refresh_error,\n variable.path IS NOT NULL as is_linked,\n variable.is_oauth as \"is_oauth?\",\n variable.account,\n ws_specific.path IS NOT NULL as ws_specific\n FROM resource\n LEFT JOIN variable ON variable.path = resource.path AND variable.workspace_id = $2\n LEFT JOIN account ON variable.account = account.id AND account.workspace_id = $2\n LEFT JOIN ws_specific ON ws_specific.path = resource.path AND ws_specific.workspace_id = $2 AND ws_specific.item_kind = 'resource'\n WHERE resource.path = $1 AND resource.workspace_id = $2",
"describe": {
"columns": [
{
@@ -35,13 +35,13 @@
},
{
"ordinal": 6,
- "name": "edited_at",
- "type_info": "Timestamptz"
+ "name": "created_by",
+ "type_info": "Varchar"
},
{
"ordinal": 7,
- "name": "created_by",
- "type_info": "Varchar"
+ "name": "edited_at",
+ "type_info": "Timestamptz"
},
{
"ordinal": 8,
@@ -77,6 +77,11 @@
"ordinal": 14,
"name": "account",
"type_info": "Int4"
+ },
+ {
+ "ordinal": 15,
+ "name": "ws_specific",
+ "type_info": "Bool"
}
],
"parameters": {
@@ -100,8 +105,9 @@
true,
null,
false,
- true
+ true,
+ null
]
},
- "hash": "41f2c271514ee254739c3a097871526adcecdd8729f28c15e0db8cd28eaa8cf0"
+ "hash": "9254e2a0e1be830fa4cab660c69a659e2eb3f3d912e1c062bb8000a2e0a653e8"
}
diff --git a/backend/.sqlx/query-9a1bf7c424154172b56bbd29b51ef468969b5452150ee6fffe31d59b52a4f1c2.json b/backend/.sqlx/query-9a1bf7c424154172b56bbd29b51ef468969b5452150ee6fffe31d59b52a4f1c2.json
new file mode 100644
index 0000000000..b4715fa36b
--- /dev/null
+++ b/backend/.sqlx/query-9a1bf7c424154172b56bbd29b51ef468969b5452150ee6fffe31d59b52a4f1c2.json
@@ -0,0 +1,23 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "INSERT INTO token\n (token_hash, token_prefix, token, email, label, expiration, super_admin, scopes, workspace_id, read_only)\n SELECT $1, $2, $3, $4, $5, $6, $7, $8, $9, $10\n WHERE $9::varchar IS NULL OR NOT EXISTS(\n SELECT 1 FROM workspace WHERE id = $9 AND deleted = true\n )",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": [
+ "Varchar",
+ "Varchar",
+ "Varchar",
+ "Varchar",
+ "Varchar",
+ "Timestamptz",
+ "Bool",
+ "TextArray",
+ "Varchar",
+ "Bool"
+ ]
+ },
+ "nullable": []
+ },
+ "hash": "9a1bf7c424154172b56bbd29b51ef468969b5452150ee6fffe31d59b52a4f1c2"
+}
diff --git a/backend/.sqlx/query-9b88e522ecbe9fa67ef83e79ec5eb5c9c87999a877fcb7f23be75d991bba6e49.json b/backend/.sqlx/query-9b88e522ecbe9fa67ef83e79ec5eb5c9c87999a877fcb7f23be75d991bba6e49.json
new file mode 100644
index 0000000000..5321166c27
--- /dev/null
+++ b/backend/.sqlx/query-9b88e522ecbe9fa67ef83e79ec5eb5c9c87999a877fcb7f23be75d991bba6e49.json
@@ -0,0 +1,22 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "SELECT last_locked_at FROM concurrency_locks WHERE id = $1",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "last_locked_at",
+ "type_info": "Timestamp"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Text"
+ ]
+ },
+ "nullable": [
+ false
+ ]
+ },
+ "hash": "9b88e522ecbe9fa67ef83e79ec5eb5c9c87999a877fcb7f23be75d991bba6e49"
+}
diff --git a/backend/.sqlx/query-9c85ba8d41bedbcb5466f44a7d4cf6b4946e1fd337f00d243f518283783833c9.json b/backend/.sqlx/query-9c85ba8d41bedbcb5466f44a7d4cf6b4946e1fd337f00d243f518283783833c9.json
new file mode 100644
index 0000000000..fce125c6d5
--- /dev/null
+++ b/backend/.sqlx/query-9c85ba8d41bedbcb5466f44a7d4cf6b4946e1fd337f00d243f518283783833c9.json
@@ -0,0 +1,22 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "SELECT bool_and(operator) FROM (\n SELECT operator FROM usr WHERE email = $1\n UNION ALL\n SELECT operator FROM workspace_invite WHERE email = $1\n ) t",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "bool_and",
+ "type_info": "Bool"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Text"
+ ]
+ },
+ "nullable": [
+ null
+ ]
+ },
+ "hash": "9c85ba8d41bedbcb5466f44a7d4cf6b4946e1fd337f00d243f518283783833c9"
+}
diff --git a/backend/.sqlx/query-9db0ee31bfae5b3c6e4ba83bd3659e138529a62ad02b22e0afddd2887d6aad72.json b/backend/.sqlx/query-9db0ee31bfae5b3c6e4ba83bd3659e138529a62ad02b22e0afddd2887d6aad72.json
new file mode 100644
index 0000000000..44a033b19c
--- /dev/null
+++ b/backend/.sqlx/query-9db0ee31bfae5b3c6e4ba83bd3659e138529a62ad02b22e0afddd2887d6aad72.json
@@ -0,0 +1,12 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "INSERT INTO workspace_diff\n (source_workspace_id, fork_workspace_id, path, kind, ahead, behind, has_changes)\n VALUES\n ('test-workspace', 'wm-fork-test-workspace', 'f/sch/runtime_only', 'schedule', 0, 1, NULL),\n ('test-workspace', 'wm-fork-test-workspace', 'f/sch/config_change', 'schedule', 0, 1, NULL),\n ('test-workspace', 'wm-fork-test-workspace', 'f/rt/runtime_only', 'http_trigger', 0, 1, NULL),\n ('test-workspace', 'wm-fork-test-workspace', 'f/rt/config_change', 'http_trigger', 0, 1, NULL)",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": []
+ },
+ "nullable": []
+ },
+ "hash": "9db0ee31bfae5b3c6e4ba83bd3659e138529a62ad02b22e0afddd2887d6aad72"
+}
diff --git a/backend/.sqlx/query-9f978c08b3a0150bb6f6ef71a6c4a1d8b126495a514ea4c0e791216aef55dc38.json b/backend/.sqlx/query-9f978c08b3a0150bb6f6ef71a6c4a1d8b126495a514ea4c0e791216aef55dc38.json
new file mode 100644
index 0000000000..6819848a06
--- /dev/null
+++ b/backend/.sqlx/query-9f978c08b3a0150bb6f6ef71a6c4a1d8b126495a514ea4c0e791216aef55dc38.json
@@ -0,0 +1,59 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "SELECT kind AS \"kind: JobKind\", count(*) AS \"count!\"\n FROM v2_job\n WHERE workspace_id = $1\n AND id <> ALL($2)\n AND parent_job IS NULL\n GROUP BY kind\n ORDER BY kind::text",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "kind: JobKind",
+ "type_info": {
+ "Custom": {
+ "name": "job_kind",
+ "kind": {
+ "Enum": [
+ "script",
+ "preview",
+ "flow",
+ "dependencies",
+ "flowpreview",
+ "script_hub",
+ "identity",
+ "flowdependencies",
+ "http",
+ "graphql",
+ "postgresql",
+ "noop",
+ "appdependencies",
+ "deploymentcallback",
+ "singlestepflow",
+ "flowscript",
+ "flownode",
+ "appscript",
+ "aiagent",
+ "unassigned_script",
+ "unassigned_flow",
+ "unassigned_singlestepflow"
+ ]
+ }
+ }
+ }
+ },
+ {
+ "ordinal": 1,
+ "name": "count!",
+ "type_info": "Int8"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Text",
+ "UuidArray"
+ ]
+ },
+ "nullable": [
+ false,
+ null
+ ]
+ },
+ "hash": "9f978c08b3a0150bb6f6ef71a6c4a1d8b126495a514ea4c0e791216aef55dc38"
+}
diff --git a/backend/.sqlx/query-a1ab1f23f49496f745d89d6c33a91c6b693afc4477a634aa84f81db91f9e03a4.json b/backend/.sqlx/query-a1ab1f23f49496f745d89d6c33a91c6b693afc4477a634aa84f81db91f9e03a4.json
deleted file mode 100644
index 0c8209f0d0..0000000000
--- a/backend/.sqlx/query-a1ab1f23f49496f745d89d6c33a91c6b693afc4477a634aa84f81db91f9e03a4.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "db_name": "PostgreSQL",
- "query": "SELECT COALESCE(\n (SELECT DISTINCT ON (s.path) s.schema FROM script s WHERE s.path = jb.runnable_path AND jb.kind = 'script' ORDER BY s.path, s.created_at DESC),\n (SELECT flow_version.schema FROM flow LEFT JOIN flow_version ON flow_version.id = flow.versions[array_upper(flow.versions, 1)] WHERE flow.path = jb.runnable_path AND jb.kind = 'flow')\n ) FROM v2_job jb\n WHERE jb.id = $1 AND jb.workspace_id = $2\n GROUP BY jb.kind, jb.runnable_path",
- "describe": {
- "columns": [
- {
- "ordinal": 0,
- "name": "coalesce",
- "type_info": "Json"
- }
- ],
- "parameters": {
- "Left": [
- "Uuid",
- "Text"
- ]
- },
- "nullable": [
- null
- ]
- },
- "hash": "a1ab1f23f49496f745d89d6c33a91c6b693afc4477a634aa84f81db91f9e03a4"
-}
diff --git a/backend/.sqlx/query-edd57b3d59ddc21b99212b5fabd4a8b793c4ae618a04220b3609c8c3c168f8fd.json b/backend/.sqlx/query-a2f6ca89faaa8d8739f67cd1aae571de793b377dd6058065f51b12c974e5b665.json
similarity index 69%
rename from backend/.sqlx/query-edd57b3d59ddc21b99212b5fabd4a8b793c4ae618a04220b3609c8c3c168f8fd.json
rename to backend/.sqlx/query-a2f6ca89faaa8d8739f67cd1aae571de793b377dd6058065f51b12c974e5b665.json
index f67e52ab6b..2358af807f 100644
--- a/backend/.sqlx/query-edd57b3d59ddc21b99212b5fabd4a8b793c4ae618a04220b3609c8c3c168f8fd.json
+++ b/backend/.sqlx/query-a2f6ca89faaa8d8739f67cd1aae571de793b377dd6058065f51b12c974e5b665.json
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
- "query": "\n SELECT usr.email, usage.executions\n FROM usr, LATERAL (\n SELECT COALESCE(SUM(c.duration_ms + 1000)/1000 , 0)::BIGINT executions\n FROM v2_job_completed c JOIN v2_job j USING (id)\n WHERE j.workspace_id = $1\n AND j.kind NOT IN ('flow', 'flowpreview', 'flownode')\n AND j.permissioned_as_email = usr.email\n AND now() - '1 week'::interval < j.created_at\n ) usage\n WHERE workspace_id = $1\n ",
+ "query": "\n SELECT usr.email, usage.executions\n FROM usr, LATERAL (\n SELECT COALESCE(SUM(c.duration_ms + 1000)/1000 , 0)::BIGINT executions\n FROM v2_job_completed c JOIN v2_job j USING (id)\n WHERE j.workspace_id = $1\n AND j.kind NOT IN ('flow', 'flowpreview', 'flownode', 'singlestepflow')\n AND j.permissioned_as_email = usr.email\n AND now() - '1 week'::interval < j.created_at\n ) usage\n WHERE workspace_id = $1\n ",
"describe": {
"columns": [
{
@@ -24,5 +24,5 @@
null
]
},
- "hash": "edd57b3d59ddc21b99212b5fabd4a8b793c4ae618a04220b3609c8c3c168f8fd"
+ "hash": "a2f6ca89faaa8d8739f67cd1aae571de793b377dd6058065f51b12c974e5b665"
}
diff --git a/backend/.sqlx/query-a420ea939b0bfe58b89f29c9eacf2d3fbbe0a140cddad3762e0b4147eb84c0b4.json b/backend/.sqlx/query-a420ea939b0bfe58b89f29c9eacf2d3fbbe0a140cddad3762e0b4147eb84c0b4.json
new file mode 100644
index 0000000000..58022cbc43
--- /dev/null
+++ b/backend/.sqlx/query-a420ea939b0bfe58b89f29c9eacf2d3fbbe0a140cddad3762e0b4147eb84c0b4.json
@@ -0,0 +1,14 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms, summary, created_by)\n VALUES ('wm-fork-stale-super', 'folder2', 'folder2', ARRAY['u/test-user-2']::varchar[], $1, '', 'test-user-2')",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": [
+ "Jsonb"
+ ]
+ },
+ "nullable": []
+ },
+ "hash": "a420ea939b0bfe58b89f29c9eacf2d3fbbe0a140cddad3762e0b4147eb84c0b4"
+}
diff --git a/backend/.sqlx/query-aba492cb21cbbd514959a16ca02ab3b62efb138edd968d90f10f9043ae9a7c62.json b/backend/.sqlx/query-aba492cb21cbbd514959a16ca02ab3b62efb138edd968d90f10f9043ae9a7c62.json
new file mode 100644
index 0000000000..291e8279fa
--- /dev/null
+++ b/backend/.sqlx/query-aba492cb21cbbd514959a16ca02ab3b62efb138edd968d90f10f9043ae9a7c62.json
@@ -0,0 +1,23 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "WITH normalized AS (\n SELECT\n jb.id,\n jb.workspace_id,\n jb.runnable_path,\n CASE\n WHEN jb.kind IN ('flow', 'script') THEN jb.kind::text\n WHEN jb.kind = 'singlestepflow' THEN\n COALESCE(\n (SELECT m->'value'->>'type'\n FROM jsonb_array_elements(jb.raw_flow->'modules') m\n WHERE m->>'id' IN ('a', 'main')\n LIMIT 1),\n 'script'\n )\n ELSE NULL\n END AS norm_kind,\n COALESCE(\n jb.runnable_id,\n CASE WHEN jb.kind = 'singlestepflow' THEN\n (SELECT ('x' || lpad(m->'value'->>'hash', 16, '0'))::bit(64)::bigint\n FROM jsonb_array_elements(jb.raw_flow->'modules') m\n WHERE m->>'id' IN ('a', 'main')\n AND m->'value'->>'hash' IS NOT NULL\n LIMIT 1)\n END\n ) AS effective_hash\n FROM v2_job jb\n WHERE jb.kind IN ('flow', 'script', 'singlestepflow')\n AND jb.workspace_id = $1 AND jb.id = ANY($2)\n )\n SELECT jsonb_build_object(\n 'kind', n.norm_kind,\n 'script_path', n.runnable_path,\n 'latest_schema', COALESCE(\n (SELECT DISTINCT ON (s.path) s.schema FROM script s WHERE s.workspace_id = $1 AND s.path = n.runnable_path AND n.norm_kind = 'script' ORDER BY s.path, s.created_at DESC),\n (SELECT flow_version.schema FROM flow LEFT JOIN flow_version ON flow_version.id = flow.versions[array_upper(flow.versions, 1)] WHERE flow.workspace_id = $1 AND flow.path = n.runnable_path AND n.norm_kind = 'flow')\n ),\n 'schemas', ARRAY(\n SELECT jsonb_build_object(\n 'script_hash', CASE WHEN COALESCE(s.hash, f.id) IS NULL THEN NULL ELSE LPAD(TO_HEX(COALESCE(s.hash, f.id)), 16, '0') END,\n 'job_ids', ARRAY_AGG(DISTINCT n2.id),\n 'schema', COALESCE(\n (ARRAY_AGG(COALESCE(s.schema, f.schema)))[1],\n CASE WHEN n.norm_kind = 'script' THEN\n (SELECT DISTINCT ON (s2.path) s2.schema FROM script s2 WHERE s2.workspace_id = $1 AND s2.path = n.runnable_path ORDER BY s2.path, s2.created_at DESC)\n END,\n CASE WHEN n.norm_kind = 'flow' THEN\n (SELECT fv.schema FROM flow LEFT JOIN flow_version fv ON fv.id = flow.versions[array_upper(flow.versions, 1)] WHERE flow.workspace_id = $1 AND flow.path = n.runnable_path)\n END\n )\n ) FROM normalized n2\n LEFT JOIN script s ON s.hash = n2.effective_hash AND n2.norm_kind = 'script'\n LEFT JOIN flow_version f ON f.id = n2.effective_hash AND n2.norm_kind = 'flow'\n WHERE n2.id = ANY(ARRAY_AGG(n.id))\n GROUP BY COALESCE(s.hash, f.id)\n )\n ) FROM normalized n\n WHERE n.norm_kind IS NOT NULL\n GROUP BY n.norm_kind, n.runnable_path",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "jsonb_build_object",
+ "type_info": "Jsonb"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Text",
+ "UuidArray"
+ ]
+ },
+ "nullable": [
+ null
+ ]
+ },
+ "hash": "aba492cb21cbbd514959a16ca02ab3b62efb138edd968d90f10f9043ae9a7c62"
+}
diff --git a/backend/.sqlx/query-b02d3563b72ee8989dea128a3c6a2be62ce1f8f7edf3793d7ad4af9eb11b648f.json b/backend/.sqlx/query-b02d3563b72ee8989dea128a3c6a2be62ce1f8f7edf3793d7ad4af9eb11b648f.json
new file mode 100644
index 0000000000..3536567e16
--- /dev/null
+++ b/backend/.sqlx/query-b02d3563b72ee8989dea128a3c6a2be62ce1f8f7edf3793d7ad4af9eb11b648f.json
@@ -0,0 +1,28 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "SELECT label, expiration FROM token WHERE token_hash = $1",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "label",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 1,
+ "name": "expiration",
+ "type_info": "Timestamptz"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Text"
+ ]
+ },
+ "nullable": [
+ true,
+ true
+ ]
+ },
+ "hash": "b02d3563b72ee8989dea128a3c6a2be62ce1f8f7edf3793d7ad4af9eb11b648f"
+}
diff --git a/backend/.sqlx/query-b32d8b364001f5d54c1c4e564aac6b49d514771191a004c21c2e0d042f9d6f4d.json b/backend/.sqlx/query-b32d8b364001f5d54c1c4e564aac6b49d514771191a004c21c2e0d042f9d6f4d.json
new file mode 100644
index 0000000000..9704af007f
--- /dev/null
+++ b/backend/.sqlx/query-b32d8b364001f5d54c1c4e564aac6b49d514771191a004c21c2e0d042f9d6f4d.json
@@ -0,0 +1,23 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "SELECT COALESCE(\n (SELECT DISTINCT ON (s.path) s.schema FROM script s WHERE s.path = norm.path AND s.workspace_id = $2 AND norm.kind = 'script' ORDER BY s.path, s.created_at DESC),\n (SELECT flow_version.schema FROM flow LEFT JOIN flow_version ON flow_version.id = flow.versions[array_upper(flow.versions, 1)] WHERE flow.path = norm.path AND flow.workspace_id = $2 AND norm.kind = 'flow')\n ) FROM (\n SELECT\n jb.runnable_path AS path,\n CASE\n WHEN jb.kind IN ('script', 'flow') THEN jb.kind::text\n WHEN jb.kind = 'singlestepflow' THEN COALESCE(\n (SELECT m->'value'->>'type' FROM jsonb_array_elements(jb.raw_flow->'modules') m WHERE m->>'id' IN ('a', 'main') LIMIT 1),\n 'script'\n )\n END AS kind\n FROM v2_job jb\n WHERE jb.id = $1 AND jb.workspace_id = $2\n ) norm\n GROUP BY norm.kind, norm.path",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "coalesce",
+ "type_info": "Json"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Uuid",
+ "Text"
+ ]
+ },
+ "nullable": [
+ null
+ ]
+ },
+ "hash": "b32d8b364001f5d54c1c4e564aac6b49d514771191a004c21c2e0d042f9d6f4d"
+}
diff --git a/backend/.sqlx/query-b620c0af0725a71fc777b39b016403751741b97a8bfc620174802ad3827bba39.json b/backend/.sqlx/query-b620c0af0725a71fc777b39b016403751741b97a8bfc620174802ad3827bba39.json
new file mode 100644
index 0000000000..c369c8c8a8
--- /dev/null
+++ b/backend/.sqlx/query-b620c0af0725a71fc777b39b016403751741b97a8bfc620174802ad3827bba39.json
@@ -0,0 +1,12 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "INSERT INTO workspace_settings (workspace_id) VALUES ('wm-fork-test-workspace')",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": []
+ },
+ "nullable": []
+ },
+ "hash": "b620c0af0725a71fc777b39b016403751741b97a8bfc620174802ad3827bba39"
+}
diff --git a/backend/.sqlx/query-b845cbe97b10c6194fd89dbf2510216da2c1b8a4ce7ee1482ff5f347c3de145e.json b/backend/.sqlx/query-b845cbe97b10c6194fd89dbf2510216da2c1b8a4ce7ee1482ff5f347c3de145e.json
new file mode 100644
index 0000000000..2101f60f5f
--- /dev/null
+++ b/backend/.sqlx/query-b845cbe97b10c6194fd89dbf2510216da2c1b8a4ce7ee1482ff5f347c3de145e.json
@@ -0,0 +1,23 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "SELECT value from resource WHERE path = $1 AND workspace_id = $2 AND resource_type = 'app_theme'",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "value",
+ "type_info": "Jsonb"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Text",
+ "Text"
+ ]
+ },
+ "nullable": [
+ true
+ ]
+ },
+ "hash": "b845cbe97b10c6194fd89dbf2510216da2c1b8a4ce7ee1482ff5f347c3de145e"
+}
diff --git a/backend/.sqlx/query-b876f26ce90e30c3510eacddb03d9dd26fac05d18183f2d623ac91ea3876dd5c.json b/backend/.sqlx/query-b876f26ce90e30c3510eacddb03d9dd26fac05d18183f2d623ac91ea3876dd5c.json
new file mode 100644
index 0000000000..e1a3640463
--- /dev/null
+++ b/backend/.sqlx/query-b876f26ce90e30c3510eacddb03d9dd26fac05d18183f2d623ac91ea3876dd5c.json
@@ -0,0 +1,16 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "\n INSERT INTO variable (workspace_id, path, value, is_secret, description, extra_perms, account)\n VALUES ($1, $2, $3, true, '', '{}'::jsonb, NULL)\n ON CONFLICT (workspace_id, path) DO UPDATE SET value = EXCLUDED.value\n ",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": [
+ "Varchar",
+ "Varchar",
+ "Varchar"
+ ]
+ },
+ "nullable": []
+ },
+ "hash": "b876f26ce90e30c3510eacddb03d9dd26fac05d18183f2d623ac91ea3876dd5c"
+}
diff --git a/backend/.sqlx/query-b8cf0655ecb679c8437ea897a28ec86cd9f3b485a1bacccbd2e5ac5266ac6f01.json b/backend/.sqlx/query-b8cf0655ecb679c8437ea897a28ec86cd9f3b485a1bacccbd2e5ac5266ac6f01.json
new file mode 100644
index 0000000000..7d84982484
--- /dev/null
+++ b/backend/.sqlx/query-b8cf0655ecb679c8437ea897a28ec86cd9f3b485a1bacccbd2e5ac5266ac6f01.json
@@ -0,0 +1,12 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "INSERT INTO usr (workspace_id, email, username, is_admin, role)\n VALUES ('wm-fork-rename-test', 'test2@windmill.dev', 'test-user-2', false, 'User')",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": []
+ },
+ "nullable": []
+ },
+ "hash": "b8cf0655ecb679c8437ea897a28ec86cd9f3b485a1bacccbd2e5ac5266ac6f01"
+}
diff --git a/backend/.sqlx/query-bce66e3f3fecda6c226556f2f3cff27702b8fc8d2850fdb262b9a2a77b468646.json b/backend/.sqlx/query-bce66e3f3fecda6c226556f2f3cff27702b8fc8d2850fdb262b9a2a77b468646.json
new file mode 100644
index 0000000000..ab4532a1f5
--- /dev/null
+++ b/backend/.sqlx/query-bce66e3f3fecda6c226556f2f3cff27702b8fc8d2850fdb262b9a2a77b468646.json
@@ -0,0 +1,101 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "SELECT email as \"email!\", login_type::text, verified as \"verified!\", super_admin as \"super_admin!\", devops as \"devops!\", name, company, username, NULL::bool as operator_only, NULL::bool as is_workspace_admin, first_time_user as \"first_time_user!\", role_source as \"role_source!\", disabled as \"disabled!\", NULL::text as workspace_id FROM password\n UNION ALL\n SELECT email as \"email!\", 'service_account'::text as login_type, true as \"verified!\", false as \"super_admin!\", false as \"devops!\", NULL::text as name, NULL::text as company, username, operator as operator_only, is_admin as is_workspace_admin, false as \"first_time_user!\", 'service_account'::text as \"role_source!\", disabled as \"disabled!\", workspace_id\n FROM usr\n WHERE is_service_account IS true\n ORDER BY \"super_admin!\" DESC, \"devops!\" DESC, \"email!\"\n LIMIT $1 OFFSET $2",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "email!",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 1,
+ "name": "login_type",
+ "type_info": "Text"
+ },
+ {
+ "ordinal": 2,
+ "name": "verified!",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 3,
+ "name": "super_admin!",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 4,
+ "name": "devops!",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 5,
+ "name": "name",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 6,
+ "name": "company",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 7,
+ "name": "username",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 8,
+ "name": "operator_only",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 9,
+ "name": "is_workspace_admin",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 10,
+ "name": "first_time_user!",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 11,
+ "name": "role_source!",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 12,
+ "name": "disabled!",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 13,
+ "name": "workspace_id",
+ "type_info": "Text"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Int8",
+ "Int8"
+ ]
+ },
+ "nullable": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ]
+ },
+ "hash": "bce66e3f3fecda6c226556f2f3cff27702b8fc8d2850fdb262b9a2a77b468646"
+}
diff --git a/backend/.sqlx/query-bcfa34cf80abea05f0c24883b9e77429c51e6166c414bcc5ce2e97fac25bcd77.json b/backend/.sqlx/query-bcfa34cf80abea05f0c24883b9e77429c51e6166c414bcc5ce2e97fac25bcd77.json
new file mode 100644
index 0000000000..045d470de5
--- /dev/null
+++ b/backend/.sqlx/query-bcfa34cf80abea05f0c24883b9e77429c51e6166c414bcc5ce2e97fac25bcd77.json
@@ -0,0 +1,98 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "\n SELECT\n id,\n args as \"args: _\",\n created_at,\n kind AS \"kind: _\"\n FROM v2_job\n WHERE workspace_id = $1\n AND (\n kind = 'unassigned_script'::JOB_KIND OR\n kind = 'unassigned_flow'::JOB_KIND OR\n kind = 'unassigned_singlestepflow'::JOB_KIND\n )\n AND trigger_kind = $2\n AND trigger = $3\n ",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "id",
+ "type_info": "Uuid"
+ },
+ {
+ "ordinal": 1,
+ "name": "args: _",
+ "type_info": "Jsonb"
+ },
+ {
+ "ordinal": 2,
+ "name": "created_at",
+ "type_info": "Timestamptz"
+ },
+ {
+ "ordinal": 3,
+ "name": "kind: _",
+ "type_info": {
+ "Custom": {
+ "name": "job_kind",
+ "kind": {
+ "Enum": [
+ "script",
+ "preview",
+ "flow",
+ "dependencies",
+ "flowpreview",
+ "script_hub",
+ "identity",
+ "flowdependencies",
+ "http",
+ "graphql",
+ "postgresql",
+ "noop",
+ "appdependencies",
+ "deploymentcallback",
+ "singlestepflow",
+ "flowscript",
+ "flownode",
+ "appscript",
+ "aiagent",
+ "unassigned_script",
+ "unassigned_flow",
+ "unassigned_singlestepflow"
+ ]
+ }
+ }
+ }
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Text",
+ {
+ "Custom": {
+ "name": "job_trigger_kind",
+ "kind": {
+ "Enum": [
+ "webhook",
+ "http",
+ "websocket",
+ "kafka",
+ "email",
+ "nats",
+ "schedule",
+ "app",
+ "ui",
+ "postgres",
+ "sqs",
+ "gcp",
+ "mqtt",
+ "nextcloud",
+ "google",
+ "ci_test",
+ "github",
+ "azure"
+ ]
+ }
+ }
+ },
+ "Text"
+ ]
+ },
+ "nullable": [
+ false,
+ true,
+ false,
+ false
+ ]
+ },
+ "hash": "bcfa34cf80abea05f0c24883b9e77429c51e6166c414bcc5ce2e97fac25bcd77"
+}
diff --git a/backend/.sqlx/query-bf601a919de299e44e6e418b2e711e24909fc206b7fd48541483e6316fe002d5.json b/backend/.sqlx/query-bf601a919de299e44e6e418b2e711e24909fc206b7fd48541483e6316fe002d5.json
new file mode 100644
index 0000000000..6ca4cbb60f
--- /dev/null
+++ b/backend/.sqlx/query-bf601a919de299e44e6e418b2e711e24909fc206b7fd48541483e6316fe002d5.json
@@ -0,0 +1,28 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "\n SELECT j.id, j.args\n FROM v2_job j\n JOIN v2_job_queue q ON j.id = q.id\n WHERE j.runnable_path = $1\n AND j.kind = 'deploymentcallback'\n AND j.workspace_id = 'test-workspace'\n ORDER BY j.created_at DESC\n ",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "id",
+ "type_info": "Uuid"
+ },
+ {
+ "ordinal": 1,
+ "name": "args",
+ "type_info": "Jsonb"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Text"
+ ]
+ },
+ "nullable": [
+ false,
+ true
+ ]
+ },
+ "hash": "bf601a919de299e44e6e418b2e711e24909fc206b7fd48541483e6316fe002d5"
+}
diff --git a/backend/.sqlx/query-c17c39add3f70218dbae38595a909d02cfabe7e0864af577df6968428ac448ef.json b/backend/.sqlx/query-c17c39add3f70218dbae38595a909d02cfabe7e0864af577df6968428ac448ef.json
new file mode 100644
index 0000000000..36be033396
--- /dev/null
+++ b/backend/.sqlx/query-c17c39add3f70218dbae38595a909d02cfabe7e0864af577df6968428ac448ef.json
@@ -0,0 +1,95 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "WITH active_users AS (SELECT distinct username as email FROM (SELECT username, timestamp, operation FROM audit_partitioned UNION ALL SELECT username, timestamp, operation FROM audit) AS a WHERE timestamp > NOW() - INTERVAL '1 month' AND (operation = 'users.login' OR operation = 'oauth.login' OR operation = 'users.token.refresh')),\n authors as (SELECT distinct email FROM usr WHERE usr.operator IS false)\n SELECT email as \"email!\", (email NOT IN (SELECT email FROM authors)) as operator_only, login_type::text, verified as \"verified!\", super_admin as \"super_admin!\", devops as \"devops!\", name, company, username, first_time_user as \"first_time_user!\", role_source as \"role_source!\", disabled as \"disabled!\", NULL::text as workspace_id\n FROM password\n WHERE email IN (SELECT email FROM active_users)\n UNION ALL\n SELECT email as \"email!\", true as operator_only, 'service_account'::text as login_type, true as \"verified!\", false as \"super_admin!\", false as \"devops!\", NULL::text as name, NULL::text as company, username, false as \"first_time_user!\", 'service_account'::text as \"role_source!\", disabled as \"disabled!\", workspace_id\n FROM usr\n WHERE is_service_account IS true\n ORDER BY \"super_admin!\" DESC, \"devops!\" DESC\n LIMIT $1 OFFSET $2",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "email!",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 1,
+ "name": "operator_only",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 2,
+ "name": "login_type",
+ "type_info": "Text"
+ },
+ {
+ "ordinal": 3,
+ "name": "verified!",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 4,
+ "name": "super_admin!",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 5,
+ "name": "devops!",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 6,
+ "name": "name",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 7,
+ "name": "company",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 8,
+ "name": "username",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 9,
+ "name": "first_time_user!",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 10,
+ "name": "role_source!",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 11,
+ "name": "disabled!",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 12,
+ "name": "workspace_id",
+ "type_info": "Text"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Int8",
+ "Int8"
+ ]
+ },
+ "nullable": [
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ ]
+ },
+ "hash": "c17c39add3f70218dbae38595a909d02cfabe7e0864af577df6968428ac448ef"
+}
diff --git a/backend/.sqlx/query-c340adb3118a002ab700cf3f5eae0e9cf6940081b556832142dd757468ca823b.json b/backend/.sqlx/query-c340adb3118a002ab700cf3f5eae0e9cf6940081b556832142dd757468ca823b.json
new file mode 100644
index 0000000000..9cadbcac89
--- /dev/null
+++ b/backend/.sqlx/query-c340adb3118a002ab700cf3f5eae0e9cf6940081b556832142dd757468ca823b.json
@@ -0,0 +1,14 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "INSERT INTO concurrency_counter(concurrency_id, job_uuids) VALUES ($1, '{}'::jsonb)",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": [
+ "Varchar"
+ ]
+ },
+ "nullable": []
+ },
+ "hash": "c340adb3118a002ab700cf3f5eae0e9cf6940081b556832142dd757468ca823b"
+}
diff --git a/backend/.sqlx/query-d16c80faa5ae1838379bc05841bdd43c59c936c5f8d801256df4860eb04d7779.json b/backend/.sqlx/query-d16c80faa5ae1838379bc05841bdd43c59c936c5f8d801256df4860eb04d7779.json
new file mode 100644
index 0000000000..ec525e63cc
--- /dev/null
+++ b/backend/.sqlx/query-d16c80faa5ae1838379bc05841bdd43c59c936c5f8d801256df4860eb04d7779.json
@@ -0,0 +1,22 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "SELECT pg_try_advisory_xact_lock($1) AS \"locked!\"",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "locked!",
+ "type_info": "Bool"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Int8"
+ ]
+ },
+ "nullable": [
+ null
+ ]
+ },
+ "hash": "d16c80faa5ae1838379bc05841bdd43c59c936c5f8d801256df4860eb04d7779"
+}
diff --git a/backend/.sqlx/query-d1dabd750d2304107a981c992b3dc52cc9f471ce8e61a00132aed2dcce8653af.json b/backend/.sqlx/query-d1dabd750d2304107a981c992b3dc52cc9f471ce8e61a00132aed2dcce8653af.json
new file mode 100644
index 0000000000..1cc8d7fa64
--- /dev/null
+++ b/backend/.sqlx/query-d1dabd750d2304107a981c992b3dc52cc9f471ce8e61a00132aed2dcce8653af.json
@@ -0,0 +1,16 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "UPDATE ws_specific SET path = $1 WHERE workspace_id = $2 AND item_kind = 'variable' AND path = $3",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": [
+ "Varchar",
+ "Text",
+ "Text"
+ ]
+ },
+ "nullable": []
+ },
+ "hash": "d1dabd750d2304107a981c992b3dc52cc9f471ce8e61a00132aed2dcce8653af"
+}
diff --git a/backend/.sqlx/query-d487c1dd1f6456e11c30ac44e3828f3316d2b994d836b1145039ee7d77391628.json b/backend/.sqlx/query-d487c1dd1f6456e11c30ac44e3828f3316d2b994d836b1145039ee7d77391628.json
new file mode 100644
index 0000000000..0a3a7084dd
--- /dev/null
+++ b/backend/.sqlx/query-d487c1dd1f6456e11c30ac44e3828f3316d2b994d836b1145039ee7d77391628.json
@@ -0,0 +1,23 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "SELECT COALESCE(\n (SELECT COUNT(*)\n FROM jsonb_object_keys(job_uuids) AS keys(key)\n WHERE key <> $2),\n 0\n )\n FROM concurrency_counter\n WHERE concurrency_id = $1",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "coalesce",
+ "type_info": "Int8"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Text",
+ "Text"
+ ]
+ },
+ "nullable": [
+ null
+ ]
+ },
+ "hash": "d487c1dd1f6456e11c30ac44e3828f3316d2b994d836b1145039ee7d77391628"
+}
diff --git a/backend/.sqlx/query-d4af472614e1b6af8defa40a79d5f584c8b114ef24fdb67a5eefb40ce17acdef.json b/backend/.sqlx/query-d4af472614e1b6af8defa40a79d5f584c8b114ef24fdb67a5eefb40ce17acdef.json
new file mode 100644
index 0000000000..23087064d3
--- /dev/null
+++ b/backend/.sqlx/query-d4af472614e1b6af8defa40a79d5f584c8b114ef24fdb67a5eefb40ce17acdef.json
@@ -0,0 +1,16 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "UPDATE v2_job_status s\n SET flow_status = JSONB_SET(s.flow_status, ARRAY['modules', s.flow_status->>'step', 'progress'], $1)\n FROM v2_job j\n WHERE s.id = $2 AND j.id = s.id AND j.workspace_id = $3",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": [
+ "Jsonb",
+ "Uuid",
+ "Text"
+ ]
+ },
+ "nullable": []
+ },
+ "hash": "d4af472614e1b6af8defa40a79d5f584c8b114ef24fdb67a5eefb40ce17acdef"
+}
diff --git a/backend/.sqlx/query-d94636ff736f9cfefb3c001acab6fb7fe3573e3aa71441d04b7cd7bba685b371.json b/backend/.sqlx/query-d94636ff736f9cfefb3c001acab6fb7fe3573e3aa71441d04b7cd7bba685b371.json
new file mode 100644
index 0000000000..9e15a5445d
--- /dev/null
+++ b/backend/.sqlx/query-d94636ff736f9cfefb3c001acab6fb7fe3573e3aa71441d04b7cd7bba685b371.json
@@ -0,0 +1,14 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "INSERT INTO script (workspace_id, path, hash, content, summary, description, language, created_by, created_at, archived, schema_validation, ws_error_handler_muted, deleted, extra_perms)\n VALUES ('wm-fork-visibility-test', 'f/folder2/myscript', 222222, 'def main():\n return 1', '', '', 'python3', 'test-user-2', NOW(), false, false, false, false, $1)",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": [
+ "Jsonb"
+ ]
+ },
+ "nullable": []
+ },
+ "hash": "d94636ff736f9cfefb3c001acab6fb7fe3573e3aa71441d04b7cd7bba685b371"
+}
diff --git a/backend/.sqlx/query-dd540bcb206d931eb19aa85059b6a64554a9a29ebc6064718072470e45a0ae28.json b/backend/.sqlx/query-dd540bcb206d931eb19aa85059b6a64554a9a29ebc6064718072470e45a0ae28.json
new file mode 100644
index 0000000000..a6b14c7b96
--- /dev/null
+++ b/backend/.sqlx/query-dd540bcb206d931eb19aa85059b6a64554a9a29ebc6064718072470e45a0ae28.json
@@ -0,0 +1,18 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "INSERT INTO usr\n (workspace_id, email, username, is_admin, operator, is_service_account)\n VALUES ($1, $2, $3, $4, $5, true)",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": [
+ "Varchar",
+ "Varchar",
+ "Varchar",
+ "Bool",
+ "Bool"
+ ]
+ },
+ "nullable": []
+ },
+ "hash": "dd540bcb206d931eb19aa85059b6a64554a9a29ebc6064718072470e45a0ae28"
+}
diff --git a/backend/.sqlx/query-debd7470025cf64cd1191e604fed9ae0ab27c8a76302a2570473046e28c91fdd.json b/backend/.sqlx/query-debd7470025cf64cd1191e604fed9ae0ab27c8a76302a2570473046e28c91fdd.json
new file mode 100644
index 0000000000..f907d4e959
--- /dev/null
+++ b/backend/.sqlx/query-debd7470025cf64cd1191e604fed9ae0ab27c8a76302a2570473046e28c91fdd.json
@@ -0,0 +1,29 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "SELECT workspace_id, path FROM app WHERE custom_path = $1 AND ($2::TEXT IS NULL OR workspace_id = $2) LIMIT 1",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "workspace_id",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 1,
+ "name": "path",
+ "type_info": "Varchar"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Text",
+ "Text"
+ ]
+ },
+ "nullable": [
+ false,
+ false
+ ]
+ },
+ "hash": "debd7470025cf64cd1191e604fed9ae0ab27c8a76302a2570473046e28c91fdd"
+}
diff --git a/backend/.sqlx/query-e001fb68c60fa736cecce52be49c9fb0714c9c91a7ea3924811d345bee94c013.json b/backend/.sqlx/query-e001fb68c60fa736cecce52be49c9fb0714c9c91a7ea3924811d345bee94c013.json
new file mode 100644
index 0000000000..d0d9bd8d95
--- /dev/null
+++ b/backend/.sqlx/query-e001fb68c60fa736cecce52be49c9fb0714c9c91a7ea3924811d345bee94c013.json
@@ -0,0 +1,66 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "SELECT label, token_prefix, expiration, created_at, last_used_at, scopes, workspace_id, read_only FROM token WHERE email = $1 AND (label != 'ephemeral-script' OR label IS NULL)\n ORDER BY created_at DESC LIMIT $2 OFFSET $3",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "label",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 1,
+ "name": "token_prefix",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 2,
+ "name": "expiration",
+ "type_info": "Timestamptz"
+ },
+ {
+ "ordinal": 3,
+ "name": "created_at",
+ "type_info": "Timestamptz"
+ },
+ {
+ "ordinal": 4,
+ "name": "last_used_at",
+ "type_info": "Timestamptz"
+ },
+ {
+ "ordinal": 5,
+ "name": "scopes",
+ "type_info": "TextArray"
+ },
+ {
+ "ordinal": 6,
+ "name": "workspace_id",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 7,
+ "name": "read_only",
+ "type_info": "Bool"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Text",
+ "Int8",
+ "Int8"
+ ]
+ },
+ "nullable": [
+ true,
+ false,
+ true,
+ false,
+ false,
+ true,
+ true,
+ false
+ ]
+ },
+ "hash": "e001fb68c60fa736cecce52be49c9fb0714c9c91a7ea3924811d345bee94c013"
+}
diff --git a/backend/.sqlx/query-e1ada31c1625b453c2ff85edbcd7ad51a4cd5cbdc2fa34038530070d6a579455.json b/backend/.sqlx/query-e1ada31c1625b453c2ff85edbcd7ad51a4cd5cbdc2fa34038530070d6a579455.json
new file mode 100644
index 0000000000..f38c023cb3
--- /dev/null
+++ b/backend/.sqlx/query-e1ada31c1625b453c2ff85edbcd7ad51a4cd5cbdc2fa34038530070d6a579455.json
@@ -0,0 +1,26 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "WITH potential AS (\n SELECT email, operator FROM usr\n UNION\n SELECT email, operator FROM workspace_invite\n ),\n per_user AS (\n SELECT email, bool_and(operator) AS only_operator FROM potential GROUP BY email\n )\n SELECT\n COUNT(*) FILTER (WHERE NOT only_operator) AS \"authors!\",\n COUNT(*) FILTER (WHERE only_operator) AS \"operators!\"\n FROM per_user",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "authors!",
+ "type_info": "Int8"
+ },
+ {
+ "ordinal": 1,
+ "name": "operators!",
+ "type_info": "Int8"
+ }
+ ],
+ "parameters": {
+ "Left": []
+ },
+ "nullable": [
+ null,
+ null
+ ]
+ },
+ "hash": "e1ada31c1625b453c2ff85edbcd7ad51a4cd5cbdc2fa34038530070d6a579455"
+}
diff --git a/backend/.sqlx/query-e2905bca184696a80357d8e4126832a902b3088d91fbbb858f6c0aa9de8a5ff7.json b/backend/.sqlx/query-e2905bca184696a80357d8e4126832a902b3088d91fbbb858f6c0aa9de8a5ff7.json
deleted file mode 100644
index cab697d254..0000000000
--- a/backend/.sqlx/query-e2905bca184696a80357d8e4126832a902b3088d91fbbb858f6c0aa9de8a5ff7.json
+++ /dev/null
@@ -1,89 +0,0 @@
-{
- "db_name": "PostgreSQL",
- "query": "SELECT\n j.id,\n j.kind AS \"kind: _\",\n COALESCE(s.path, f.path) AS \"script_path!\",\n COALESCE(s.hash, f.id) AS \"script_hash!: _\",\n COALESCE(jc.started_at, jq.scheduled_for, make_date(1970, 1, 1)) AS \"scheduled_for!: _\",\n args AS input,\n COALESCE(s.schema, f.schema) AS \"schema: _\"\n FROM v2_job j\n LEFT JOIN script s ON j.runnable_id = s.hash AND j.kind = 'script'\n LEFT JOIN flow_version f ON j.runnable_id = f.id AND j.runnable_path = f.path AND j.kind = 'flow'\n LEFT JOIN v2_job_completed jc ON jc.id = j.id\n LEFT JOIN v2_job_queue jq ON jq.id = j.id\n WHERE j.id = ANY($1)\n AND j.workspace_id = $2\n AND COALESCE(s.hash, f.id) IS NOT NULL\n AND COALESCE(s.path, f.path) IS NOT NULL",
- "describe": {
- "columns": [
- {
- "ordinal": 0,
- "name": "id",
- "type_info": "Uuid"
- },
- {
- "ordinal": 1,
- "name": "kind: _",
- "type_info": {
- "Custom": {
- "name": "job_kind",
- "kind": {
- "Enum": [
- "script",
- "preview",
- "flow",
- "dependencies",
- "flowpreview",
- "script_hub",
- "identity",
- "flowdependencies",
- "http",
- "graphql",
- "postgresql",
- "noop",
- "appdependencies",
- "deploymentcallback",
- "singlestepflow",
- "flowscript",
- "flownode",
- "appscript",
- "aiagent",
- "unassigned_script",
- "unassigned_flow",
- "unassigned_singlestepflow"
- ]
- }
- }
- }
- },
- {
- "ordinal": 2,
- "name": "script_path!",
- "type_info": "Varchar"
- },
- {
- "ordinal": 3,
- "name": "script_hash!: _",
- "type_info": "Int8"
- },
- {
- "ordinal": 4,
- "name": "scheduled_for!: _",
- "type_info": "Timestamptz"
- },
- {
- "ordinal": 5,
- "name": "input",
- "type_info": "Jsonb"
- },
- {
- "ordinal": 6,
- "name": "schema: _",
- "type_info": "Json"
- }
- ],
- "parameters": {
- "Left": [
- "UuidArray",
- "Text"
- ]
- },
- "nullable": [
- false,
- false,
- null,
- null,
- null,
- true,
- null
- ]
- },
- "hash": "e2905bca184696a80357d8e4126832a902b3088d91fbbb858f6c0aa9de8a5ff7"
-}
diff --git a/backend/.sqlx/query-e48bf61e59268f95ec389ef30eac15271cbba1601c589a6e0f3399875438aae9.json b/backend/.sqlx/query-e48bf61e59268f95ec389ef30eac15271cbba1601c589a6e0f3399875438aae9.json
new file mode 100644
index 0000000000..1cb5e8166c
--- /dev/null
+++ b/backend/.sqlx/query-e48bf61e59268f95ec389ef30eac15271cbba1601c589a6e0f3399875438aae9.json
@@ -0,0 +1,12 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "UPDATE password SET super_admin = true WHERE email = 'test2@windmill.dev'",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": []
+ },
+ "nullable": []
+ },
+ "hash": "e48bf61e59268f95ec389ef30eac15271cbba1601c589a6e0f3399875438aae9"
+}
diff --git a/backend/.sqlx/query-e4d2a19d56e561e903c883d503f1864734ae5329e31a39d7af7bad4e3e2fe5b2.json b/backend/.sqlx/query-e4d2a19d56e561e903c883d503f1864734ae5329e31a39d7af7bad4e3e2fe5b2.json
new file mode 100644
index 0000000000..39e187e227
--- /dev/null
+++ b/backend/.sqlx/query-e4d2a19d56e561e903c883d503f1864734ae5329e31a39d7af7bad4e3e2fe5b2.json
@@ -0,0 +1,42 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "SELECT\n EXISTS(SELECT 1 FROM ws_specific\n WHERE workspace_id = $1 AND item_kind = 'variable' AND path = $3) AS \"src_ws!\",\n EXISTS(SELECT 1 FROM ws_specific\n WHERE workspace_id = $2 AND item_kind = 'variable' AND path = $3) AS \"tgt_ws!\",\n EXISTS(SELECT 1 FROM variable\n WHERE workspace_id = $1 AND path = $3) AS \"src_var!\",\n EXISTS(SELECT 1 FROM variable\n WHERE workspace_id = $2 AND path = $3) AS \"tgt_var!\"",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "src_ws!",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 1,
+ "name": "tgt_ws!",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 2,
+ "name": "src_var!",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 3,
+ "name": "tgt_var!",
+ "type_info": "Bool"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Text",
+ "Text",
+ "Text"
+ ]
+ },
+ "nullable": [
+ null,
+ null,
+ null,
+ null
+ ]
+ },
+ "hash": "e4d2a19d56e561e903c883d503f1864734ae5329e31a39d7af7bad4e3e2fe5b2"
+}
diff --git a/backend/.sqlx/query-e59a15166ccacc601d059f2da379fe3ecc4cf60e4a928acaaa78da3ed65949b7.json b/backend/.sqlx/query-e59a15166ccacc601d059f2da379fe3ecc4cf60e4a928acaaa78da3ed65949b7.json
new file mode 100644
index 0000000000..4b0102591d
--- /dev/null
+++ b/backend/.sqlx/query-e59a15166ccacc601d059f2da379fe3ecc4cf60e4a928acaaa78da3ed65949b7.json
@@ -0,0 +1,22 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "SELECT COALESCE((SELECT COUNT(*) FROM jsonb_object_keys(job_uuids)), 0)\n FROM concurrency_counter WHERE concurrency_id = $1",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "coalesce",
+ "type_info": "Int8"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Text"
+ ]
+ },
+ "nullable": [
+ null
+ ]
+ },
+ "hash": "e59a15166ccacc601d059f2da379fe3ecc4cf60e4a928acaaa78da3ed65949b7"
+}
diff --git a/backend/.sqlx/query-e70cbc2a48bfc5c7d2018b9367eadc104e87126c57230c9ed8eb3987e54b53a1.json b/backend/.sqlx/query-e70cbc2a48bfc5c7d2018b9367eadc104e87126c57230c9ed8eb3987e54b53a1.json
new file mode 100644
index 0000000000..48981ce580
--- /dev/null
+++ b/backend/.sqlx/query-e70cbc2a48bfc5c7d2018b9367eadc104e87126c57230c9ed8eb3987e54b53a1.json
@@ -0,0 +1,23 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "SELECT EXISTS(SELECT 1 FROM flow_version WHERE id = $1 AND workspace_id = $2)",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "exists",
+ "type_info": "Bool"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Int8",
+ "Text"
+ ]
+ },
+ "nullable": [
+ null
+ ]
+ },
+ "hash": "e70cbc2a48bfc5c7d2018b9367eadc104e87126c57230c9ed8eb3987e54b53a1"
+}
diff --git a/backend/.sqlx/query-e8597d72fc73446d6ac1e76dffcbc6a1c77e754825b59ca4bc79c4e675bed8e5.json b/backend/.sqlx/query-e8597d72fc73446d6ac1e76dffcbc6a1c77e754825b59ca4bc79c4e675bed8e5.json
new file mode 100644
index 0000000000..b67b639a79
--- /dev/null
+++ b/backend/.sqlx/query-e8597d72fc73446d6ac1e76dffcbc6a1c77e754825b59ca4bc79c4e675bed8e5.json
@@ -0,0 +1,58 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "SELECT kind AS \"kind: JobKind\", args::text AS \"args!\"\n FROM v2_job WHERE id = $1",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "kind: JobKind",
+ "type_info": {
+ "Custom": {
+ "name": "job_kind",
+ "kind": {
+ "Enum": [
+ "script",
+ "preview",
+ "flow",
+ "dependencies",
+ "flowpreview",
+ "script_hub",
+ "identity",
+ "flowdependencies",
+ "http",
+ "graphql",
+ "postgresql",
+ "noop",
+ "appdependencies",
+ "deploymentcallback",
+ "singlestepflow",
+ "flowscript",
+ "flownode",
+ "appscript",
+ "aiagent",
+ "unassigned_script",
+ "unassigned_flow",
+ "unassigned_singlestepflow"
+ ]
+ }
+ }
+ }
+ },
+ {
+ "ordinal": 1,
+ "name": "args!",
+ "type_info": "Text"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Uuid"
+ ]
+ },
+ "nullable": [
+ false,
+ null
+ ]
+ },
+ "hash": "e8597d72fc73446d6ac1e76dffcbc6a1c77e754825b59ca4bc79c4e675bed8e5"
+}
diff --git a/backend/.sqlx/query-bef2776351e8489559609d390b92d688519e8af27b228202c872061cbda7e30a.json b/backend/.sqlx/query-e9cd99ea990f5aabf1278856c2bf4395c98b8858b686ea9f30e77bfb00c7bf9c.json
similarity index 74%
rename from backend/.sqlx/query-bef2776351e8489559609d390b92d688519e8af27b228202c872061cbda7e30a.json
rename to backend/.sqlx/query-e9cd99ea990f5aabf1278856c2bf4395c98b8858b686ea9f30e77bfb00c7bf9c.json
index d75ffd0339..7050274345 100644
--- a/backend/.sqlx/query-bef2776351e8489559609d390b92d688519e8af27b228202c872061cbda7e30a.json
+++ b/backend/.sqlx/query-e9cd99ea990f5aabf1278856c2bf4395c98b8858b686ea9f30e77bfb00c7bf9c.json
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
- "query": "SELECT scheduled_for FROM v2_job_queue INNER JOIN concurrency_key ON concurrency_key.job_id = v2_job_queue.id\n WHERE key = $1 AND running = false AND canceled_by IS NULL AND scheduled_for >= $2",
+ "query": "SELECT scheduled_for FROM v2_job_queue INNER JOIN concurrency_key ON concurrency_key.job_id = v2_job_queue.id\n WHERE key = $1 AND running = false AND canceled_by IS NULL AND scheduled_for >= $2\n ORDER BY scheduled_for ASC\n LIMIT 1000",
"describe": {
"columns": [
{
@@ -19,5 +19,5 @@
false
]
},
- "hash": "bef2776351e8489559609d390b92d688519e8af27b228202c872061cbda7e30a"
+ "hash": "e9cd99ea990f5aabf1278856c2bf4395c98b8858b686ea9f30e77bfb00c7bf9c"
}
diff --git a/backend/.sqlx/query-ea132d80fdb6525f192797fd77b3f5343e68856dfede19597813893b7e99ead1.json b/backend/.sqlx/query-ea132d80fdb6525f192797fd77b3f5343e68856dfede19597813893b7e99ead1.json
new file mode 100644
index 0000000000..593408b1d4
--- /dev/null
+++ b/backend/.sqlx/query-ea132d80fdb6525f192797fd77b3f5343e68856dfede19597813893b7e99ead1.json
@@ -0,0 +1,17 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "INSERT INTO public.flow_version(id, workspace_id, path, schema, value, created_by) VALUES ($1, $2, $3, '{}'::jsonb, $4, 'system')",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": [
+ "Int8",
+ "Varchar",
+ "Varchar",
+ "Jsonb"
+ ]
+ },
+ "nullable": []
+ },
+ "hash": "ea132d80fdb6525f192797fd77b3f5343e68856dfede19597813893b7e99ead1"
+}
diff --git a/backend/.sqlx/query-ed82ffc4d806a19519701e39abc43e8e250cc9772cca4a7dae3442be86f4060c.json b/backend/.sqlx/query-ed82ffc4d806a19519701e39abc43e8e250cc9772cca4a7dae3442be86f4060c.json
new file mode 100644
index 0000000000..b1f0fc0cff
--- /dev/null
+++ b/backend/.sqlx/query-ed82ffc4d806a19519701e39abc43e8e250cc9772cca4a7dae3442be86f4060c.json
@@ -0,0 +1,53 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "UPDATE token SET last_used_at = now() WHERE\n token_hash = $1\n AND (expiration > NOW() OR expiration IS NULL)\n AND (workspace_id IS NULL OR workspace_id = $2)\n RETURNING owner, email, super_admin, scopes, label, read_only",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "owner",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 1,
+ "name": "email",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 2,
+ "name": "super_admin",
+ "type_info": "Bool"
+ },
+ {
+ "ordinal": 3,
+ "name": "scopes",
+ "type_info": "TextArray"
+ },
+ {
+ "ordinal": 4,
+ "name": "label",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 5,
+ "name": "read_only",
+ "type_info": "Bool"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Text",
+ "Text"
+ ]
+ },
+ "nullable": [
+ true,
+ true,
+ false,
+ true,
+ true,
+ false
+ ]
+ },
+ "hash": "ed82ffc4d806a19519701e39abc43e8e250cc9772cca4a7dae3442be86f4060c"
+}
diff --git a/backend/.sqlx/query-eff46e24a907607ee1c0292d0736e41d84e90e099639bdbdb4987cef4d0936c1.json b/backend/.sqlx/query-eff46e24a907607ee1c0292d0736e41d84e90e099639bdbdb4987cef4d0936c1.json
new file mode 100644
index 0000000000..e8559f1829
--- /dev/null
+++ b/backend/.sqlx/query-eff46e24a907607ee1c0292d0736e41d84e90e099639bdbdb4987cef4d0936c1.json
@@ -0,0 +1,22 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "SELECT value FROM background_task_state WHERE name = $1",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "value",
+ "type_info": "Jsonb"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Text"
+ ]
+ },
+ "nullable": [
+ false
+ ]
+ },
+ "hash": "eff46e24a907607ee1c0292d0736e41d84e90e099639bdbdb4987cef4d0936c1"
+}
diff --git a/backend/.sqlx/query-f5e98ff83301b89f33e4454ae944da1977030cf2db9dadd372188902bb23062f.json b/backend/.sqlx/query-f5e98ff83301b89f33e4454ae944da1977030cf2db9dadd372188902bb23062f.json
new file mode 100644
index 0000000000..ac592bafbe
--- /dev/null
+++ b/backend/.sqlx/query-f5e98ff83301b89f33e4454ae944da1977030cf2db9dadd372188902bb23062f.json
@@ -0,0 +1,34 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "\n SELECT\n workspace_id,\n (elem->>'installation_id')::bigint as \"installation_id!\",\n COALESCE((elem->>'provisioned_by_admin')::bool, false) as \"provisioned_by_admin!\"\n FROM workspace_settings,\n LATERAL jsonb_array_elements(git_app_installations) AS elem\n WHERE (elem->>'installation_id')::bigint = ANY($1)\n ",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "workspace_id",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 1,
+ "name": "installation_id!",
+ "type_info": "Int8"
+ },
+ {
+ "ordinal": 2,
+ "name": "provisioned_by_admin!",
+ "type_info": "Bool"
+ }
+ ],
+ "parameters": {
+ "Left": [
+ "Int8Array"
+ ]
+ },
+ "nullable": [
+ false,
+ null,
+ null
+ ]
+ },
+ "hash": "f5e98ff83301b89f33e4454ae944da1977030cf2db9dadd372188902bb23062f"
+}
diff --git a/backend/.sqlx/query-750f09238caf114d01f752a145fd4289f04f3961d5035d42f3fd0bd2f87eed1a.json b/backend/.sqlx/query-f6eff53e00b33310bd9626b44b72af91a2da474ddf622f15f4f618dfcc7f1c39.json
similarity index 82%
rename from backend/.sqlx/query-750f09238caf114d01f752a145fd4289f04f3961d5035d42f3fd0bd2f87eed1a.json
rename to backend/.sqlx/query-f6eff53e00b33310bd9626b44b72af91a2da474ddf622f15f4f618dfcc7f1c39.json
index c72f20178c..6829ce231d 100644
--- a/backend/.sqlx/query-750f09238caf114d01f752a145fd4289f04f3961d5035d42f3fd0bd2f87eed1a.json
+++ b/backend/.sqlx/query-f6eff53e00b33310bd9626b44b72af91a2da474ddf622f15f4f618dfcc7f1c39.json
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
- "query": "\n UPDATE v2_job_status f SET flow_status = JSONB_SET(flow_status, ARRAY['user_states'], JSONB_SET(COALESCE(flow_status->'user_states', '{}'::jsonb), ARRAY[$1], $2))\n FROM v2_job j\n WHERE f.id = $3 AND f.id = j.id AND j.workspace_id = $4 AND kind IN ('flow', 'flowpreview', 'flownode') RETURNING 1\n ",
+ "query": "\n UPDATE v2_job_status f SET flow_status = JSONB_SET(flow_status, ARRAY['user_states'], JSONB_SET(COALESCE(flow_status->'user_states', '{}'::jsonb), ARRAY[$1], $2))\n FROM v2_job j\n WHERE f.id = $3 AND f.id = j.id AND j.workspace_id = $4 AND kind IN ('flow', 'flowpreview', 'flownode', 'singlestepflow') RETURNING 1\n ",
"describe": {
"columns": [
{
@@ -21,5 +21,5 @@
null
]
},
- "hash": "750f09238caf114d01f752a145fd4289f04f3961d5035d42f3fd0bd2f87eed1a"
+ "hash": "f6eff53e00b33310bd9626b44b72af91a2da474ddf622f15f4f618dfcc7f1c39"
}
diff --git a/backend/.sqlx/query-f7281933e08b9e378eccfc539f3fb1c984c4154322cb6ef102cc303ebb1e3b46.json b/backend/.sqlx/query-f7281933e08b9e378eccfc539f3fb1c984c4154322cb6ef102cc303ebb1e3b46.json
new file mode 100644
index 0000000000..bde28d2b86
--- /dev/null
+++ b/backend/.sqlx/query-f7281933e08b9e378eccfc539f3fb1c984c4154322cb6ef102cc303ebb1e3b46.json
@@ -0,0 +1,15 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "INSERT INTO background_task_state (name, value) VALUES ($1, $2)\n ON CONFLICT (name) DO NOTHING",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": [
+ "Text",
+ "Jsonb"
+ ]
+ },
+ "nullable": []
+ },
+ "hash": "f7281933e08b9e378eccfc539f3fb1c984c4154322cb6ef102cc303ebb1e3b46"
+}
diff --git a/backend/.sqlx/query-f7e273ab42c1632ffe4d127b447813046e86b8768a276f1b724d32721424fa08.json b/backend/.sqlx/query-f7e273ab42c1632ffe4d127b447813046e86b8768a276f1b724d32721424fa08.json
new file mode 100644
index 0000000000..dee0944da5
--- /dev/null
+++ b/backend/.sqlx/query-f7e273ab42c1632ffe4d127b447813046e86b8768a276f1b724d32721424fa08.json
@@ -0,0 +1,15 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "DELETE FROM ws_specific\n WHERE workspace_id = $1 AND item_kind = 'resource' AND path = $2",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": [
+ "Text",
+ "Text"
+ ]
+ },
+ "nullable": []
+ },
+ "hash": "f7e273ab42c1632ffe4d127b447813046e86b8768a276f1b724d32721424fa08"
+}
diff --git a/backend/.sqlx/query-f83cf3c87a1e80d4a0a7f236c4fa472a4f9ae49e9c2e601c41cd7229ecde765a.json b/backend/.sqlx/query-f83cf3c87a1e80d4a0a7f236c4fa472a4f9ae49e9c2e601c41cd7229ecde765a.json
new file mode 100644
index 0000000000..f2aff2c94d
--- /dev/null
+++ b/backend/.sqlx/query-f83cf3c87a1e80d4a0a7f236c4fa472a4f9ae49e9c2e601c41cd7229ecde765a.json
@@ -0,0 +1,12 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "INSERT INTO workspace_diff\n (source_workspace_id, fork_workspace_id, path, kind, ahead, behind, has_changes)\n VALUES ('test-workspace', 'wm-fork-stale-super', 'f/folder2/myscript', 'script', 1, 0, NULL)",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": []
+ },
+ "nullable": []
+ },
+ "hash": "f83cf3c87a1e80d4a0a7f236c4fa472a4f9ae49e9c2e601c41cd7229ecde765a"
+}
diff --git a/backend/.sqlx/query-f8f756bc498e5f084851f98e1e8d8c74cdc672a903d566baf5ac5ef50a4da1bd.json b/backend/.sqlx/query-f8f756bc498e5f084851f98e1e8d8c74cdc672a903d566baf5ac5ef50a4da1bd.json
new file mode 100644
index 0000000000..e4125ddab9
--- /dev/null
+++ b/backend/.sqlx/query-f8f756bc498e5f084851f98e1e8d8c74cdc672a903d566baf5ac5ef50a4da1bd.json
@@ -0,0 +1,32 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "SELECT memory, worker, native_mode FROM worker_ping WHERE ping_at > now() - interval '2 minutes'",
+ "describe": {
+ "columns": [
+ {
+ "ordinal": 0,
+ "name": "memory",
+ "type_info": "Int8"
+ },
+ {
+ "ordinal": 1,
+ "name": "worker",
+ "type_info": "Varchar"
+ },
+ {
+ "ordinal": 2,
+ "name": "native_mode",
+ "type_info": "Bool"
+ }
+ ],
+ "parameters": {
+ "Left": []
+ },
+ "nullable": [
+ true,
+ false,
+ false
+ ]
+ },
+ "hash": "f8f756bc498e5f084851f98e1e8d8c74cdc672a903d566baf5ac5ef50a4da1bd"
+}
diff --git a/backend/.sqlx/query-fb1399c1dc171ec6bb24fee3477a1d606d9725e20e9c2d76a0887fadfd87f8df.json b/backend/.sqlx/query-fb1399c1dc171ec6bb24fee3477a1d606d9725e20e9c2d76a0887fadfd87f8df.json
deleted file mode 100644
index a2362be620..0000000000
--- a/backend/.sqlx/query-fb1399c1dc171ec6bb24fee3477a1d606d9725e20e9c2d76a0887fadfd87f8df.json
+++ /dev/null
@@ -1,25 +0,0 @@
-{
- "db_name": "PostgreSQL",
- "query": "SELECT EXISTS(SELECT 1 FROM app WHERE custom_path = $1 AND ($2::TEXT IS NULL OR workspace_id = $2) AND NOT (path = $3 AND workspace_id = $4))",
- "describe": {
- "columns": [
- {
- "ordinal": 0,
- "name": "exists",
- "type_info": "Bool"
- }
- ],
- "parameters": {
- "Left": [
- "Text",
- "Text",
- "Text",
- "Text"
- ]
- },
- "nullable": [
- null
- ]
- },
- "hash": "fb1399c1dc171ec6bb24fee3477a1d606d9725e20e9c2d76a0887fadfd87f8df"
-}
diff --git a/backend/.sqlx/query-fdcfa286819f77f9e147599036ef4bd26057dc6792a9952678e5b785e4fff9a4.json b/backend/.sqlx/query-fdcfa286819f77f9e147599036ef4bd26057dc6792a9952678e5b785e4fff9a4.json
new file mode 100644
index 0000000000..b1f15f4c06
--- /dev/null
+++ b/backend/.sqlx/query-fdcfa286819f77f9e147599036ef4bd26057dc6792a9952678e5b785e4fff9a4.json
@@ -0,0 +1,12 @@
+{
+ "db_name": "PostgreSQL",
+ "query": "INSERT INTO http_trigger (workspace_id, path, edited_by, edited_at, route_path,\n route_path_key, script_path, is_flow, http_method, request_type,\n authentication_method, mode, permissioned_as)\n VALUES\n ('test-workspace', 'f/rt/runtime_only', 'test-user', NOW(), 'foo', 'foo',\n 'f/scripts/y', false, 'get', 'sync',\n 'none', 'enabled', 'u/test-user'),\n ('wm-fork-test-workspace', 'f/rt/runtime_only', 'test-user', NOW(), 'foo', 'foo',\n 'f/scripts/y', false, 'get', 'sync',\n 'none', 'disabled', 'u/test-user')",
+ "describe": {
+ "columns": [],
+ "parameters": {
+ "Left": []
+ },
+ "nullable": []
+ },
+ "hash": "fdcfa286819f77f9e147599036ef4bd26057dc6792a9952678e5b785e4fff9a4"
+}
diff --git a/backend/Cargo.lock b/backend/Cargo.lock
index ba2f0f37b8..079944ac88 100644
--- a/backend/Cargo.lock
+++ b/backend/Cargo.lock
@@ -2,16 +2,6 @@
# It is not intended for manual editing.
version = 4
-[[package]]
-name = "Inflector"
-version = "0.11.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
-dependencies = [
- "lazy_static",
- "regex",
-]
-
[[package]]
name = "addr2line"
version = "0.25.1"
@@ -21,12 +11,6 @@ dependencies = [
"gimli",
]
-[[package]]
-name = "adler"
-version = "1.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
-
[[package]]
name = "adler2"
version = "2.0.1"
@@ -39,24 +23,10 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
dependencies = [
- "crypto-common 0.1.7",
+ "crypto-common",
"generic-array",
]
-[[package]]
-name = "aead-gcm-stream"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e70c8dec860340effb00f6945c49c0daaa6dac963602750db862eabb74bf7886"
-dependencies = [
- "aead",
- "aes 0.8.3",
- "cipher 0.4.4",
- "ctr",
- "ghash",
- "subtle",
-]
-
[[package]]
name = "aes"
version = "0.7.5"
@@ -71,9 +41,9 @@ dependencies = [
[[package]]
name = "aes"
-version = "0.8.3"
+version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2"
+checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
dependencies = [
"cfg-if",
"cipher 0.4.4",
@@ -87,22 +57,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
dependencies = [
"aead",
- "aes 0.8.3",
+ "aes 0.8.4",
"cipher 0.4.4",
"ctr",
"ghash",
"subtle",
]
-[[package]]
-name = "aes-kw"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "69fa2b352dcefb5f7f3a5fb840e02665d311d878955380515e4fd50095dd3d8c"
-dependencies = [
- "aes 0.8.3",
-]
-
[[package]]
name = "ahash"
version = "0.7.8"
@@ -279,9 +240,6 @@ name = "arrayvec"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
-dependencies = [
- "serde",
-]
[[package]]
name = "arrow"
@@ -494,29 +452,10 @@ dependencies = [
]
[[package]]
-name = "ash"
-version = "0.37.3+1.3.251"
+name = "ascii"
+version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "39e9c3835d686b0a6084ab4234fcd1b07dbf6e4767dce60874b12356a25ecd4a"
-dependencies = [
- "libloading 0.7.4",
-]
-
-[[package]]
-name = "asn1-rs"
-version = "0.5.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f6fd5ddaf0351dff5b8da21b2fb4ff8e08ddd02857f0bf69c47639106c0fff0"
-dependencies = [
- "asn1-rs-derive 0.4.0",
- "asn1-rs-impl 0.1.0",
- "displaydoc",
- "nom 7.1.3",
- "num-traits",
- "rusticata-macros",
- "thiserror 1.0.69",
- "time",
-]
+checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16"
[[package]]
name = "asn1-rs"
@@ -524,28 +463,16 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048"
dependencies = [
- "asn1-rs-derive 0.5.1",
- "asn1-rs-impl 0.2.0",
+ "asn1-rs-derive",
+ "asn1-rs-impl",
"displaydoc",
- "nom 7.1.3",
+ "nom",
"num-traits",
"rusticata-macros",
"thiserror 1.0.69",
"time",
]
-[[package]]
-name = "asn1-rs-derive"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 1.0.109",
- "synstructure 0.12.6",
-]
-
[[package]]
name = "asn1-rs-derive"
version = "0.5.1"
@@ -555,18 +482,7 @@ dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
- "synstructure 0.13.2",
-]
-
-[[package]]
-name = "asn1-rs-impl"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 1.0.109",
+ "synstructure",
]
[[package]]
@@ -582,11 +498,10 @@ dependencies = [
[[package]]
name = "ast_node"
-version = "0.9.9"
+version = "3.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f9184f2b369b3e8625712493c89b785881f27eedc6cde480a81883cef78868b2"
+checksum = "0a184645bcc6f52d69d8e7639720699c6a99efb711f886e251ed1d16db8dd90e"
dependencies = [
- "proc-macro2",
"quote",
"swc_macros_common",
"syn 2.0.117",
@@ -705,7 +620,7 @@ checksum = "850b60ddcc664dcd848f8a2fa8436ab9336e051d6dd2b3f21f897dd8e9c24703"
dependencies = [
"base64 0.22.1",
"bytes",
- "http 1.4.0",
+ "http 1.4.1",
"rand 0.8.5",
"reqwest 0.12.28",
"serde",
@@ -812,9 +727,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "autocfg"
-version = "1.5.0"
+version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
+checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
[[package]]
name = "aws-config"
@@ -837,7 +752,7 @@ dependencies = [
"bytes",
"fastrand",
"hex",
- "http 1.4.0",
+ "http 1.4.1",
"ring 0.17.14",
"time",
"tokio",
@@ -860,9 +775,9 @@ dependencies = [
[[package]]
name = "aws-lc-rs"
-version = "1.16.3"
+version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f"
+checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00"
dependencies = [
"aws-lc-sys",
"zeroize",
@@ -870,9 +785,9 @@ dependencies = [
[[package]]
name = "aws-lc-sys"
-version = "0.40.0"
+version = "0.41.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7"
+checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4"
dependencies = [
"cc",
"cmake",
@@ -898,7 +813,7 @@ dependencies = [
"bytes",
"bytes-utils",
"fastrand",
- "http 1.4.0",
+ "http 1.4.1",
"http-body 1.0.1",
"percent-encoding",
"pin-project-lite",
@@ -925,7 +840,7 @@ dependencies = [
"bytes",
"fastrand",
"http 0.2.12",
- "http 1.4.0",
+ "http 1.4.1",
"regex-lite",
"tracing",
]
@@ -1000,7 +915,7 @@ dependencies = [
"aws-types",
"fastrand",
"http 0.2.12",
- "http 1.4.0",
+ "http 1.4.1",
"regex-lite",
"tracing",
"url",
@@ -1025,7 +940,7 @@ dependencies = [
"bytes",
"fastrand",
"http 0.2.12",
- "http 1.4.0",
+ "http 1.4.1",
"regex-lite",
"tracing",
]
@@ -1071,7 +986,7 @@ dependencies = [
"bytes",
"fastrand",
"http 0.2.12",
- "http 1.4.0",
+ "http 1.4.1",
"regex-lite",
"tracing",
]
@@ -1095,7 +1010,7 @@ dependencies = [
"bytes",
"fastrand",
"http 0.2.12",
- "http 1.4.0",
+ "http 1.4.1",
"regex-lite",
"tracing",
]
@@ -1137,9 +1052,9 @@ dependencies = [
"bytes",
"form_urlencoded",
"hex",
- "hmac 0.12.1",
+ "hmac",
"http 0.2.12",
- "http 1.4.0",
+ "http 1.4.1",
"percent-encoding",
"sha2 0.10.9",
"time",
@@ -1182,7 +1097,7 @@ dependencies = [
"futures-core",
"futures-util",
"http 0.2.12",
- "http 1.4.0",
+ "http 1.4.1",
"http-body 0.4.6",
"percent-encoding",
"pin-project-lite",
@@ -1202,7 +1117,7 @@ dependencies = [
"bytes-utils",
"futures-core",
"futures-util",
- "http 1.4.0",
+ "http 1.4.1",
"http-body 1.0.1",
"http-body-util",
"percent-encoding",
@@ -1221,12 +1136,12 @@ dependencies = [
"aws-smithy-runtime-api",
"aws-smithy-types",
"h2 0.3.27",
- "h2 0.4.13",
+ "h2 0.4.14",
"http 0.2.12",
- "http 1.4.0",
+ "http 1.4.1",
"http-body 0.4.6",
"hyper 0.14.32",
- "hyper 1.9.0",
+ "hyper 1.10.1",
"hyper-rustls 0.24.2",
"hyper-rustls 0.27.9",
"hyper-util",
@@ -1293,7 +1208,7 @@ dependencies = [
"bytes",
"fastrand",
"http 0.2.12",
- "http 1.4.0",
+ "http 1.4.1",
"http-body 0.4.6",
"http-body 1.0.1",
"http-body-util",
@@ -1313,7 +1228,7 @@ dependencies = [
"aws-smithy-types",
"bytes",
"http 0.2.12",
- "http 1.4.0",
+ "http 1.4.1",
"pin-project-lite",
"tokio",
"tracing",
@@ -1326,12 +1241,12 @@ version = "1.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "576b0d6991c9c32bc14fc340582ef148311f924d41815f641a308b5d11e8e7cd"
dependencies = [
- "base64-simd 0.8.0",
+ "base64-simd",
"bytes",
"bytes-utils",
"futures-core",
"http 0.2.12",
- "http 1.4.0",
+ "http 1.4.1",
"http-body 0.4.6",
"http-body 1.0.1",
"http-body-util",
@@ -1389,7 +1304,7 @@ dependencies = [
"axum-core 0.4.5",
"bytes",
"futures-util",
- "http 1.4.0",
+ "http 1.4.1",
"http-body 1.0.1",
"http-body-util",
"itoa",
@@ -1408,19 +1323,19 @@ dependencies = [
[[package]]
name = "axum"
-version = "0.8.4"
+version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5"
+checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90"
dependencies = [
"axum-core 0.5.6",
"axum-macros",
"bytes",
"form_urlencoded",
"futures-util",
- "http 1.4.0",
+ "http 1.4.1",
"http-body 1.0.1",
"http-body-util",
- "hyper 1.9.0",
+ "hyper 1.10.1",
"hyper-util",
"itoa",
"matchit 0.8.4",
@@ -1429,8 +1344,7 @@ dependencies = [
"multer",
"percent-encoding",
"pin-project-lite",
- "rustversion",
- "serde",
+ "serde_core",
"serde_json",
"serde_path_to_error",
"serde_urlencoded",
@@ -1451,7 +1365,7 @@ dependencies = [
"async-trait",
"bytes",
"futures-util",
- "http 1.4.0",
+ "http 1.4.1",
"http-body 1.0.1",
"http-body-util",
"mime",
@@ -1470,7 +1384,7 @@ checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1"
dependencies = [
"bytes",
"futures-core",
- "http 1.4.0",
+ "http 1.4.1",
"http-body 1.0.1",
"http-body-util",
"mime",
@@ -1518,7 +1432,7 @@ dependencies = [
"addr2line",
"cfg-if",
"libc",
- "miniz_oxide 0.8.9",
+ "miniz_oxide",
"object",
"rustc-demangle",
"windows-link 0.2.1",
@@ -1554,22 +1468,13 @@ version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
-[[package]]
-name = "base64-simd"
-version = "0.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "781dd20c3aff0bd194fe7d2a977dd92f21c173891f3a03b677359e5fa457e5d5"
-dependencies = [
- "simd-abstraction",
-]
-
[[package]]
name = "base64-simd"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195"
dependencies = [
- "outref 0.5.2",
+ "outref",
"vsimd",
]
@@ -1581,9 +1486,9 @@ checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
[[package]]
name = "better_scoped_tls"
-version = "0.1.2"
+version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "297b153aa5e573b5863108a6ddc9d5c968bd0b20e75cc614ee9821d2f45679c7"
+checksum = "7cd228125315b132eed175bf47619ac79b945b26e56b848ba203ae4ea8603609"
dependencies = [
"scoped-tls",
]
@@ -1610,33 +1515,13 @@ dependencies = [
"serde",
]
-[[package]]
-name = "bindgen"
-version = "0.70.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f"
-dependencies = [
- "bitflags 2.9.4",
- "cexpr",
- "clang-sys",
- "itertools 0.13.0",
- "log",
- "prettyplease",
- "proc-macro2",
- "quote",
- "regex",
- "rustc-hash 1.1.0",
- "shlex",
- "syn 2.0.117",
-]
-
[[package]]
name = "bindgen"
version = "0.71.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3"
dependencies = [
- "bitflags 2.9.4",
+ "bitflags 2.11.1",
"cexpr",
"clang-sys",
"itertools 0.13.0",
@@ -1646,7 +1531,7 @@ dependencies = [
"quote",
"regex",
"rustc-hash 2.1.2",
- "shlex",
+ "shlex 1.3.0",
"syn 2.0.117",
]
@@ -1656,7 +1541,7 @@ version = "0.72.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
dependencies = [
- "bitflags 2.9.4",
+ "bitflags 2.11.1",
"cexpr",
"clang-sys",
"itertools 0.13.0",
@@ -1666,19 +1551,10 @@ dependencies = [
"quote",
"regex",
"rustc-hash 2.1.2",
- "shlex",
+ "shlex 1.3.0",
"syn 2.0.117",
]
-[[package]]
-name = "bit-set"
-version = "0.5.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
-dependencies = [
- "bit-vec 0.6.3",
-]
-
[[package]]
name = "bit-set"
version = "0.8.0"
@@ -1708,11 +1584,11 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
-version = "2.9.4"
+version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
+checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
dependencies = [
- "serde",
+ "serde_core",
]
[[package]]
@@ -1759,19 +1635,13 @@ dependencies = [
"cpufeatures 0.3.0",
]
-[[package]]
-name = "block"
-version = "0.1.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
-
[[package]]
name = "block-buffer"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
dependencies = [
- "block-padding 0.2.1",
+ "block-padding",
"generic-array",
]
@@ -1784,22 +1654,13 @@ dependencies = [
"generic-array",
]
-[[package]]
-name = "block-buffer"
-version = "0.12.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be"
-dependencies = [
- "hybrid-array",
-]
-
[[package]]
name = "block-modes"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e"
dependencies = [
- "block-padding 0.2.1",
+ "block-padding",
"cipher 0.3.0",
]
@@ -1809,15 +1670,6 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae"
-[[package]]
-name = "block-padding"
-version = "0.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93"
-dependencies = [
- "generic-array",
-]
-
[[package]]
name = "bollard"
version = "0.18.1"
@@ -1830,9 +1682,9 @@ dependencies = [
"futures-core",
"futures-util",
"hex",
- "http 1.4.0",
+ "http 1.4.1",
"http-body-util",
- "hyper 1.9.0",
+ "hyper 1.10.1",
"hyper-named-pipe",
"hyper-util",
"hyperlocal",
@@ -1895,7 +1747,7 @@ checksum = "cfd1e3f8955a5d7de9fab72fc8373fade9fb8a703968cb200ae3dc6cf08e185a"
dependencies = [
"borsh-derive",
"bytes",
- "cfg_aliases 0.2.1",
+ "cfg_aliases",
]
[[package]]
@@ -1921,17 +1773,6 @@ dependencies = [
"syn 2.0.117",
]
-[[package]]
-name = "brotli"
-version = "6.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b"
-dependencies = [
- "alloc-no-stdlib",
- "alloc-stdlib",
- "brotli-decompressor 4.0.3",
-]
-
[[package]]
name = "brotli"
version = "7.0.0"
@@ -1945,13 +1786,13 @@ dependencies = [
[[package]]
name = "brotli"
-version = "8.0.2"
+version = "8.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560"
+checksum = "8119e4516436f5708bbc474a9d395bf12f1b5395e93a92a56e647ac3388c8610"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
- "brotli-decompressor 5.0.0",
+ "brotli-decompressor 5.0.1",
]
[[package]]
@@ -1966,14 +1807,23 @@ dependencies = [
[[package]]
name = "brotli-decompressor"
-version = "5.0.0"
+version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03"
+checksum = "5962523e1b92ce1b5e793d9169b9943eece10d39f62550bc04bb605d75b94924"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
]
+[[package]]
+name = "bs58"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4"
+dependencies = [
+ "tinyvec",
+]
+
[[package]]
name = "bstr"
version = "1.12.1"
@@ -1987,18 +1837,18 @@ dependencies = [
[[package]]
name = "btoi"
-version = "0.4.3"
+version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9dd6407f73a9b8b6162d8a2ef999fe6afd7cc15902ebf42c5cd296addf17e0ad"
+checksum = "3b5ab9db53bcda568284df0fd39f6eac24ad6f7ba7ff1168b9e76eba6576b976"
dependencies = [
"num-traits",
]
[[package]]
name = "bumpalo"
-version = "3.20.2"
+version = "3.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
+checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
dependencies = [
"allocator-api2",
]
@@ -2072,6 +1922,16 @@ dependencies = [
"serde",
]
+[[package]]
+name = "bytes-str"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c60b5ce37e0b883c37eb89f79a1e26fbe9c1081945d024eee93e8d91a7e18b3"
+dependencies = [
+ "bytes",
+ "serde",
+]
+
[[package]]
name = "bytes-utils"
version = "0.1.4"
@@ -2107,12 +1967,6 @@ dependencies = [
"pkg-config",
]
-[[package]]
-name = "cache_control"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1bf2a5fb3207c12b5d208ebc145f967fea5cac41a021c37417ccc31ba40f39ee"
-
[[package]]
name = "candle-core"
version = "0.9.2"
@@ -2124,7 +1978,7 @@ dependencies = [
"gemm",
"half",
"libm",
- "memmap2 0.9.10",
+ "memmap2",
"num-traits",
"num_cpus",
"rand 0.9.0",
@@ -2132,7 +1986,7 @@ dependencies = [
"rayon",
"safetensors",
"thiserror 2.0.18",
- "yoke 0.8.2",
+ "yoke",
"zip",
]
@@ -2171,15 +2025,6 @@ dependencies = [
"tracing",
]
-[[package]]
-name = "capacity_builder"
-version = "0.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "58ec49028cb308564429cd8fac4ef21290067a0afe8f5955330a8d487d0d790c"
-dependencies = [
- "itoa",
-]
-
[[package]]
name = "capacity_builder"
version = "0.5.0"
@@ -2187,8 +2032,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f2d24a6dcf0cd402a21b65d35340f3a49ff3475dc5fdac91d22d2733e6641c6"
dependencies = [
"capacity_builder_macros",
- "ecow",
- "hipstr",
"itoa",
]
@@ -2203,24 +2046,24 @@ dependencies = [
]
[[package]]
-name = "cbc"
-version = "0.1.2"
+name = "castaway"
+version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6"
+checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a"
dependencies = [
- "cipher 0.4.4",
+ "rustversion",
]
[[package]]
name = "cc"
-version = "1.2.61"
+version = "1.2.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
+checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f"
dependencies = [
"find-msvc-tools",
"jobserver",
"libc",
- "shlex",
+ "shlex 2.0.1",
]
[[package]]
@@ -2241,7 +2084,7 @@ version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
dependencies = [
- "nom 7.1.3",
+ "nom",
]
[[package]]
@@ -2250,12 +2093,6 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
-[[package]]
-name = "cfg_aliases"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
-
[[package]]
name = "cfg_aliases"
version = "0.2.1"
@@ -2322,7 +2159,7 @@ version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
- "crypto-common 0.1.7",
+ "crypto-common",
"inout",
]
@@ -2334,7 +2171,7 @@ checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
dependencies = [
"glob",
"libc",
- "libloading 0.8.9",
+ "libloading",
]
[[package]]
@@ -2365,7 +2202,7 @@ version = "4.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9"
dependencies = [
- "heck 0.5.0",
+ "heck",
"proc-macro2",
"quote",
"syn 2.0.117",
@@ -2377,15 +2214,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
-[[package]]
-name = "clipboard-win"
-version = "5.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4"
-dependencies = [
- "error-code",
-]
-
[[package]]
name = "cmake"
version = "0.1.58"
@@ -2395,49 +2223,6 @@ dependencies = [
"cc",
]
-[[package]]
-name = "cmov"
-version = "0.5.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f88a43d011fc4a6876cb7344703e297c71dda42494fee094d5f7c76bf13f746"
-
-[[package]]
-name = "codespan-reporting"
-version = "0.11.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
-dependencies = [
- "termcolor",
- "unicode-width 0.1.14",
-]
-
-[[package]]
-name = "color-print"
-version = "0.3.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3aa954171903797d5623e047d9ab69d91b493657917bdfb8c2c80ecaf9cdb6f4"
-dependencies = [
- "color-print-proc-macro",
-]
-
-[[package]]
-name = "color-print-proc-macro"
-version = "0.3.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "692186b5ebe54007e45a59aea47ece9eb4108e141326c304cdc91699a7118a22"
-dependencies = [
- "nom 7.1.3",
- "proc-macro2",
- "quote",
- "syn 2.0.117",
-]
-
-[[package]]
-name = "color_quant"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
-
[[package]]
name = "colorchoice"
version = "1.0.5"
@@ -2464,6 +2249,19 @@ dependencies = [
"unicode-width 0.2.2",
]
+[[package]]
+name = "compact_str"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f"
+dependencies = [
+ "castaway",
+ "cfg-if",
+ "itoa",
+ "ryu",
+ "static_assertions",
+]
+
[[package]]
name = "concurrent-queue"
version = "2.5.0"
@@ -2498,12 +2296,6 @@ version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
-[[package]]
-name = "const-oid"
-version = "0.10.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
-
[[package]]
name = "const-random"
version = "0.1.18"
@@ -2624,17 +2416,6 @@ version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
-[[package]]
-name = "core-graphics-types"
-version = "0.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf"
-dependencies = [
- "bitflags 1.3.2",
- "core-foundation 0.9.4",
- "libc",
-]
-
[[package]]
name = "cpufeatures"
version = "0.2.17"
@@ -2788,25 +2569,16 @@ dependencies = [
"typenum",
]
-[[package]]
-name = "crypto-common"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710"
-dependencies = [
- "hybrid-array",
-]
-
[[package]]
name = "csv"
-version = "1.3.1"
+version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf"
+checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938"
dependencies = [
"csv-core",
"itoa",
"ryu",
- "serde",
+ "serde_core",
]
[[package]]
@@ -2828,12 +2600,18 @@ dependencies = [
]
[[package]]
-name = "ctutils"
-version = "0.4.2"
+name = "curl-sys"
+version = "0.4.88+curl-8.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e"
+checksum = "644816de6547255eff4e491a1dda1c19b7237f00b62a61e6e64859ce4f2906d0"
dependencies = [
- "cmov",
+ "cc",
+ "libc",
+ "libz-sys",
+ "openssl-sys",
+ "pkg-config",
+ "vcpkg",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -2846,7 +2624,7 @@ dependencies = [
"cpufeatures 0.2.17",
"curve25519-dalek-derive",
"digest 0.10.7",
- "fiat-crypto 0.2.9",
+ "fiat-crypto",
"rustc_version 0.4.1",
"subtle",
"zeroize",
@@ -2863,17 +2641,6 @@ dependencies = [
"syn 2.0.117",
]
-[[package]]
-name = "d3d12"
-version = "0.20.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b28bfe653d79bd16c77f659305b195b82bb5ce0c0eb2a4846b82ddbd77586813"
-dependencies = [
- "bitflags 2.9.4",
- "libloading 0.8.9",
- "winapi",
-]
-
[[package]]
name = "darling"
version = "0.13.4"
@@ -2904,16 +2671,6 @@ dependencies = [
"darling_macro 0.20.11",
]
-[[package]]
-name = "darling"
-version = "0.21.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0"
-dependencies = [
- "darling_core 0.21.3",
- "darling_macro 0.21.3",
-]
-
[[package]]
name = "darling"
version = "0.23.0"
@@ -2966,20 +2723,6 @@ dependencies = [
"syn 2.0.117",
]
-[[package]]
-name = "darling_core"
-version = "0.21.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4"
-dependencies = [
- "fnv",
- "ident_case",
- "proc-macro2",
- "quote",
- "strsim 0.11.1",
- "syn 2.0.117",
-]
-
[[package]]
name = "darling_core"
version = "0.23.0"
@@ -3026,17 +2769,6 @@ dependencies = [
"syn 2.0.117",
]
-[[package]]
-name = "darling_macro"
-version = "0.21.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81"
-dependencies = [
- "darling_core 0.21.3",
- "quote",
- "syn 2.0.117",
-]
-
[[package]]
name = "darling_macro"
version = "0.23.0"
@@ -3050,22 +2782,9 @@ dependencies = [
[[package]]
name = "dashmap"
-version = "5.5.3"
+version = "6.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
-dependencies = [
- "cfg-if",
- "hashbrown 0.14.5",
- "lock_api",
- "once_cell",
- "parking_lot_core",
-]
-
-[[package]]
-name = "dashmap"
-version = "6.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf"
+checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c"
dependencies = [
"cfg-if",
"crossbeam-utils",
@@ -3150,7 +2869,7 @@ checksum = "61fe34f401bd03724a1f96d12108144f8cd495a3cdda2bf5e091822fb80b7e66"
dependencies = [
"arrow",
"async-trait",
- "dashmap 6.1.0",
+ "dashmap",
"datafusion-common",
"datafusion-common-runtime",
"datafusion-datasource",
@@ -3356,7 +3075,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06f004d100f49a3658c9da6fb0c3a9b760062d96cd4ad82ccc3b7b69a9fb2f84"
dependencies = [
"arrow",
- "dashmap 6.1.0",
+ "dashmap",
"datafusion-common",
"datafusion-expr",
"futures",
@@ -3652,7 +3371,7 @@ checksum = "ad229a134c7406c057ece00c8743c0c34b97f4e72f78b475fe17b66c5e14fa4f"
dependencies = [
"arrow",
"async-trait",
- "dashmap 6.1.0",
+ "dashmap",
"datafusion-common",
"datafusion-common-runtime",
"datafusion-execution",
@@ -3709,19 +3428,18 @@ dependencies = [
[[package]]
name = "deno_ast"
-version = "0.44.0"
+version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eebc7aaabfdb3ddcad32aee1b62d250149dc8b35dfbdccbb125df2bdc62da952"
+checksum = "c72e0409b3dbd60a5bf296cbc273a8e36bb3a3aab6abac389f1891e187d5ce14"
dependencies = [
- "base64 0.21.7",
- "deno_error",
+ "base64 0.22.1",
+ "capacity_builder",
+ "deno_error 0.7.3",
"deno_media_type",
"deno_terminal",
"dprint-swc-ext",
- "once_cell",
"percent-encoding",
"serde",
- "sourcemap 9.3.2",
"swc_atoms",
"swc_common",
"swc_config",
@@ -3729,6 +3447,7 @@ dependencies = [
"swc_ecma_ast",
"swc_ecma_codegen",
"swc_ecma_codegen_macros",
+ "swc_ecma_lexer",
"swc_ecma_loader",
"swc_ecma_parser",
"swc_ecma_transforms_base",
@@ -3741,146 +3460,45 @@ dependencies = [
"swc_ecma_visit",
"swc_eq_ignore_macros",
"swc_macros_common",
+ "swc_sourcemap",
"swc_visit",
- "swc_visit_macros",
"text_lines",
"thiserror 2.0.18",
- "unicode-width 0.1.14",
- "url",
-]
-
-[[package]]
-name = "deno_broadcast_channel"
-version = "0.184.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "33db5dacb54c6fda4c5ea4103c5687b76a51202343379af8b21120ba9d20f3c2"
-dependencies = [
- "async-trait",
- "deno_core",
- "deno_error",
- "thiserror 2.0.18",
- "tokio",
- "uuid",
-]
-
-[[package]]
-name = "deno_cache"
-version = "0.122.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e0daca6ec4e6142a994d38e7bc587dda7948fa00e6194b671a5d4340f5a918a3"
-dependencies = [
- "async-trait",
- "deno_core",
- "deno_error",
- "rusqlite",
- "serde",
- "sha2 0.10.9",
- "thiserror 2.0.18",
- "tokio",
-]
-
-[[package]]
-name = "deno_cache_dir"
-version = "0.17.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "27429da4d0e601baaa41415a43468d49a586645d13497f12e8a9346f9f6b1347"
-dependencies = [
- "async-trait",
- "base32",
- "base64 0.21.7",
- "boxed_error",
- "cache_control",
- "chrono",
- "data-url",
- "deno_error",
- "deno_media_type",
- "deno_path_util",
- "http 1.4.0",
- "indexmap 2.14.0",
- "log",
- "once_cell",
- "parking_lot",
- "serde",
- "serde_json",
- "sha2 0.10.9",
- "sys_traits",
- "thiserror 1.0.69",
- "url",
-]
-
-[[package]]
-name = "deno_canvas"
-version = "0.59.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "35ca8f93d60d96d6f6cb0da632303afb98567accf07d9b6f8d2ef88617589d9e"
-dependencies = [
- "deno_core",
- "deno_error",
- "deno_webgpu",
- "image",
- "serde",
- "thiserror 2.0.18",
-]
-
-[[package]]
-name = "deno_config"
-version = "0.46.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08fe512a72c4300bd997c6849450a1f050da0c909a2a4fbdc44891647392bacf"
-dependencies = [
- "boxed_error",
- "capacity_builder 0.5.0",
- "deno_error",
- "deno_package_json",
- "deno_path_util",
- "deno_semver",
- "glob",
- "ignore",
- "import_map",
- "indexmap 2.14.0",
- "jsonc-parser",
- "log",
- "percent-encoding",
- "phf 0.11.3",
- "serde",
- "serde_json",
- "sys_traits",
- "thiserror 2.0.18",
+ "unicode-width 0.2.2",
"url",
]
[[package]]
name = "deno_console"
-version = "0.190.0"
+version = "0.209.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "94352b8d75c288a26ef748ad0ddae07e181109374a02c547850f96eef76b5389"
+checksum = "66c0b8a65dcb7b38c22e5969c6454b3cb0839b7dcfb7a4f0d904de871a4c4416"
dependencies = [
"deno_core",
]
[[package]]
name = "deno_core"
-version = "0.336.0"
+version = "0.352.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fdd50476c4325d5fa52bb906804a1e35b127d2a1dcf674e3447b53dcf25525bf"
+checksum = "bf78f3f72ac8e09b18a588bc26a1b3c5ab853e7fb489889b2f5000654d34db5d"
dependencies = [
"anyhow",
"az",
"bincode",
- "bit-set 0.5.3",
- "bit-vec 0.6.3",
+ "bit-set",
+ "bit-vec 0.8.0",
"bytes",
- "capacity_builder 0.1.3",
+ "capacity_builder",
"cooked-waker",
"deno_core_icudata",
- "deno_error",
+ "deno_error 0.6.1",
"deno_ops",
"deno_path_util",
"deno_unsync",
"futures",
"indexmap 2.14.0",
"libc",
- "memoffset",
"parking_lot",
"percent-encoding",
"pin-project",
@@ -3888,7 +3506,7 @@ dependencies = [
"serde_json",
"serde_v8",
"smallvec",
- "sourcemap 8.0.1",
+ "sourcemap",
"static_assertions",
"thiserror 2.0.18",
"tokio",
@@ -3903,69 +3521,13 @@ version = "0.74.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe4dccb6147bb3f3ba0c7a48e993bfeb999d2c2e47a81badee80e2b370c8d695"
-[[package]]
-name = "deno_cron"
-version = "0.70.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e8ec283bef14bcf655b209619766bdeab67f2a5e093991cca73f5d502f7bf6e8"
-dependencies = [
- "anyhow",
- "async-trait",
- "chrono",
- "deno_core",
- "deno_error",
- "saffron",
- "thiserror 2.0.18",
- "tokio",
-]
-
-[[package]]
-name = "deno_crypto"
-version = "0.204.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4f0493142a437e49b46aa8e08d715942076ff48c3cb776f0b015b4224ed0d37a"
-dependencies = [
- "aes 0.8.3",
- "aes-gcm",
- "aes-kw",
- "base64 0.21.7",
- "cbc",
- "const-oid 0.9.6",
- "ctr",
- "curve25519-dalek",
- "deno_core",
- "deno_error",
- "deno_web",
- "ed448-goldilocks",
- "elliptic-curve",
- "num-traits",
- "once_cell",
- "p256",
- "p384",
- "p521",
- "rand 0.8.5",
- "ring 0.17.14",
- "rsa",
- "sec1",
- "serde",
- "serde_bytes",
- "sha1",
- "sha2 0.10.9",
- "signature",
- "spki",
- "thiserror 2.0.18",
- "tokio",
- "uuid",
- "x25519-dalek",
-]
-
[[package]]
name = "deno_error"
-version = "0.5.5"
+version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c23dbc46d5804814b08b4675838f9884e3a52916987ec5105af36d42f9911b5"
+checksum = "612ec3fc481fea759141b0c57810889b0a4fb6fee8f10748677bfe492fd30486"
dependencies = [
- "deno_error_macro",
+ "deno_error_macro 0.6.1",
"libc",
"serde",
"serde_json",
@@ -3974,10 +3536,20 @@ dependencies = [
]
[[package]]
-name = "deno_error_macro"
-version = "0.5.5"
+name = "deno_error"
+version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "babccedee31ce7e57c3e6dff2cb3ab8d68c49d0df8222fe0d11d628e65192790"
+checksum = "3007d3f1ea92ea503324ae15883aac0c2de2b8cf6fead62203ff6a67161007ab"
+dependencies = [
+ "deno_error_macro 0.7.3",
+ "libc",
+]
+
+[[package]]
+name = "deno_error_macro"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8380a4224d5d2c3f84da4d764c4326cac62e9a1e3d4960442d29136fc07be863"
dependencies = [
"proc-macro2",
"quote",
@@ -3985,27 +3557,49 @@ dependencies = [
]
[[package]]
-name = "deno_fetch"
-version = "0.214.0"
+name = "deno_error_macro"
+version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3df032ca1f7f06a5cc459189b960793f64d415ddc9f59f262e0ad5059865002d"
+checksum = "9b565e60a9685cdf312c888665b5f8647ac692a7da7e058a5e2268a466da8eaf"
dependencies = [
- "base64 0.21.7",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "deno_features"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "487773bb24b92f3b88d1c9ef0d4d15888641a2e2ea4d4cdfd6fda12cae26317c"
+dependencies = [
+ "deno_core",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "deno_fetch"
+version = "0.233.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5cbe158bec955790105ded69978b1245b97c65092f7bf184add344c0e78efffa"
+dependencies = [
+ "base64 0.22.1",
"bytes",
"data-url",
"deno_core",
- "deno_error",
+ "deno_error 0.6.1",
"deno_fs",
"deno_path_util",
"deno_permissions",
"deno_tls",
"dyn-clone",
"error_reporter",
- "h2 0.4.13",
+ "h2 0.4.14",
"hickory-resolver",
- "http 1.4.0",
+ "http 1.4.1",
"http-body-util",
- "hyper 1.9.0",
+ "hyper 1.10.1",
"hyper-rustls 0.27.9",
"hyper-util",
"ipnet",
@@ -4018,45 +3612,23 @@ dependencies = [
"tokio-rustls 0.26.4",
"tokio-socks",
"tokio-util",
+ "tokio-vsock",
"tower 0.5.3",
"tower-http",
"tower-service",
]
-[[package]]
-name = "deno_ffi"
-version = "0.177.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bfbdc4e55c79ec1bc8a3ac72313e6f70d76340222f1e50c5c91e050296f83544"
-dependencies = [
- "deno_core",
- "deno_error",
- "deno_permissions",
- "dlopen2 0.6.1",
- "dynasmrt",
- "libffi",
- "libffi-sys",
- "log",
- "num-bigint",
- "serde",
- "serde-value",
- "serde_json",
- "thiserror 2.0.18",
- "tokio",
- "winapi",
-]
-
[[package]]
name = "deno_fs"
-version = "0.100.0"
+version = "0.119.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c82f79b71403b93b248727a89746026104b3a5ac1d82e79a02af6fa8e8487666"
+checksum = "5ca83a95cea7bcdf19dac1888ef6f04aff69d251640a4d085bc6089c06b4b181"
dependencies = [
"async-trait",
"base32",
"boxed_error",
"deno_core",
- "deno_error",
+ "deno_error 0.6.1",
"deno_io",
"deno_path_util",
"deno_permissions",
@@ -4072,58 +3644,21 @@ dependencies = [
"windows-sys 0.59.0",
]
-[[package]]
-name = "deno_http"
-version = "0.188.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d0b7e7a3bcac31ebd4677a96318003a98f0fda4613f6ba6d7f5ba57928727191"
-dependencies = [
- "async-compression",
- "async-trait",
- "base64 0.21.7",
- "brotli 6.0.0",
- "bytes",
- "cache_control",
- "deno_core",
- "deno_error",
- "deno_net",
- "deno_websocket",
- "flate2",
- "http 0.2.12",
- "http 1.4.0",
- "httparse",
- "hyper 0.14.32",
- "hyper 1.9.0",
- "hyper-util",
- "itertools 0.10.5",
- "memmem",
- "mime",
- "once_cell",
- "percent-encoding",
- "phf 0.11.3",
- "pin-project",
- "ring 0.17.14",
- "scopeguard",
- "serde",
- "smallvec",
- "thiserror 2.0.18",
- "tokio",
- "tokio-util",
-]
-
[[package]]
name = "deno_io"
-version = "0.100.0"
+version = "0.119.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e72489fe0dcada08047611d1ab92db1baebf7b606ab7c78790f622ecb30e22b"
+checksum = "597aac25be261a1bd545d6f47a80439c60e600c076f9847b3718a592883b2a73"
dependencies = [
"async-trait",
"deno_core",
- "deno_error",
+ "deno_error 0.6.1",
+ "deno_subprocess_windows",
"filetime",
"fs3",
"libc",
"log",
+ "nix 0.27.1",
"once_cell",
"os_pipe",
"parking_lot",
@@ -4135,88 +3670,24 @@ dependencies = [
"windows-sys 0.59.0",
]
-[[package]]
-name = "deno_kv"
-version = "0.98.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b0e3930d0195a3350c05eb9a4bc619ec598ea84ad8bdb9b6c3e4bef798e7cf34"
-dependencies = [
- "anyhow",
- "async-trait",
- "base64 0.21.7",
- "boxed_error",
- "bytes",
- "chrono",
- "deno_core",
- "deno_error",
- "deno_fetch",
- "deno_path_util",
- "deno_permissions",
- "deno_tls",
- "denokv_proto",
- "denokv_remote",
- "denokv_sqlite",
- "faster-hex",
- "http 1.4.0",
- "http-body-util",
- "log",
- "num-bigint",
- "prost",
- "prost-build",
- "rand 0.8.5",
- "rusqlite",
- "serde",
- "thiserror 2.0.18",
- "url",
-]
-
-[[package]]
-name = "deno_lockfile"
-version = "0.24.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "632e835a53ed667d62fdd766c5780fe8361c831d3e3fbf1a760a0b7896657587"
-dependencies = [
- "deno_semver",
- "serde",
- "serde_json",
- "thiserror 2.0.18",
-]
-
[[package]]
name = "deno_media_type"
-version = "0.2.5"
+version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "600222d059ab31ff31182b3e12615df2134a9e01605836b78ad8df91ba39eab3"
+checksum = "9fd0af4161f90b092feb363864a64d7c74e0efc13a15905d0d09df73bb72a123"
dependencies = [
"data-url",
"serde",
"url",
]
-[[package]]
-name = "deno_napi"
-version = "0.121.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "13f30bf147cc46dba87e3088d037cf99a2845ead1138033d3b346178cb781558"
-dependencies = [
- "deno_core",
- "deno_error",
- "deno_permissions",
- "libc",
- "libloading 0.7.4",
- "log",
- "napi_sym",
- "thiserror 2.0.18",
- "windows-sys 0.59.0",
-]
-
[[package]]
name = "deno_native_certs"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86bc737e098a45aa5742d51ce694ac7236a1e69fb0d9df8c862e9b4c9583c5f9"
dependencies = [
- "dlopen2 0.7.0",
+ "dlopen2",
"dlopen2_derive",
"once_cell",
"rustls-native-certs 0.7.3",
@@ -4225,12 +3696,13 @@ dependencies = [
[[package]]
name = "deno_net"
-version = "0.182.0"
+version = "0.201.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ab869063cbfe428a707511835865d55247886eb175659e538af4d5096c3d4d9d"
+checksum = "eb88f1ea2762065d6cc0316372acc22696ab6004943aa029f80546f9faf1b491"
dependencies = [
"deno_core",
- "deno_error",
+ "deno_error 0.6.1",
+ "deno_features",
"deno_permissions",
"deno_tls",
"hickory-proto",
@@ -4239,191 +3711,39 @@ dependencies = [
"quinn",
"rustls-tokio-stream",
"serde",
+ "sha2 0.10.9",
"socket2 0.5.10",
"thiserror 2.0.18",
"tokio",
-]
-
-[[package]]
-name = "deno_node"
-version = "0.128.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9638e803a668b0a5793ff94c9b2e82c54a05d9fc510901e9f3093d2d63dbdaab"
-dependencies = [
- "aead-gcm-stream",
- "aes 0.8.3",
- "async-trait",
- "base64 0.21.7",
- "blake2",
- "boxed_error",
- "brotli 6.0.0",
- "bytes",
- "cbc",
- "const-oid 0.9.6",
- "ctr",
- "data-encoding",
- "deno_core",
- "deno_error",
- "deno_fetch",
- "deno_fs",
- "deno_io",
- "deno_net",
- "deno_package_json",
- "deno_path_util",
- "deno_permissions",
- "deno_process",
- "deno_whoami",
- "der",
- "digest 0.10.7",
- "dsa",
- "ecb",
- "ecdsa",
- "ed25519-dalek",
- "elliptic-curve",
- "errno",
- "faster-hex",
- "h2 0.4.13",
- "hkdf",
- "http 1.4.0",
- "http-body-util",
- "hyper 1.9.0",
- "hyper-util",
- "idna",
- "indexmap 2.14.0",
- "ipnetwork",
- "k256",
- "lazy-regex",
- "libc",
- "libz-sys",
- "md-5 0.10.6",
- "md4",
- "memchr",
- "node_resolver",
- "num-bigint",
- "num-bigint-dig",
- "num-integer",
- "num-traits",
- "once_cell",
- "p224",
- "p256",
- "p384",
- "path-clean",
- "pbkdf2",
- "pkcs8",
- "rand 0.8.5",
- "regex",
- "ring 0.17.14",
- "ripemd",
- "rsa",
- "scrypt",
- "sec1",
- "serde",
- "sha1",
- "sha2 0.10.9",
- "sha3",
- "signature",
- "simd-json",
- "sm3",
- "spki",
- "stable_deref_trait",
- "sys_traits",
- "thiserror 2.0.18",
- "tokio",
- "tokio-eld",
- "url",
- "webpki-root-certs 0.26.11",
- "winapi",
- "windows-sys 0.59.0",
- "x25519-dalek",
- "x509-parser 0.15.1",
- "yoke 0.7.5",
-]
-
-[[package]]
-name = "deno_npm"
-version = "0.27.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4adceb4c34f10e837d0e3ae76e88dddefb13e83c05c1ef1699fa5519241c9d27"
-dependencies = [
- "async-trait",
- "capacity_builder 0.5.0",
- "deno_error",
- "deno_lockfile",
- "deno_semver",
- "futures",
- "log",
- "monch",
- "serde",
- "serde_json",
- "thiserror 2.0.18",
+ "tokio-vsock",
"url",
+ "web-transport-proto",
]
[[package]]
name = "deno_ops"
-version = "0.212.0"
+version = "0.228.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c2d328067139909aa81522a5d90f119368b541fbddd73ab630e4d9f777865f0d"
+checksum = "8bf8dbe5abf37d270bb853c5dfe45fbe3b1b6c453877cc11d7fe84e9862a6dbc"
dependencies = [
"indexmap 2.14.0",
"proc-macro-rules",
"proc-macro2",
"quote",
"stringcase",
- "strum 0.25.0",
- "strum_macros 0.25.3",
+ "strum",
+ "strum_macros",
"syn 2.0.117",
"thiserror 2.0.18",
]
-[[package]]
-name = "deno_os"
-version = "0.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e8371d206f6265c4e0b74116c1a58cc8c464c45da0b43c1e8c19a911e88feb2b"
-dependencies = [
- "deno_core",
- "deno_error",
- "deno_path_util",
- "deno_permissions",
- "deno_telemetry",
- "libc",
- "netif",
- "ntapi",
- "once_cell",
- "serde",
- "signal-hook",
- "signal-hook-registry",
- "thiserror 2.0.18",
- "tokio",
- "winapi",
-]
-
-[[package]]
-name = "deno_package_json"
-version = "0.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d07d26dbfcc01e636aef86f9baff7faf5338398e74d283d8fe01e39068f48049"
-dependencies = [
- "boxed_error",
- "deno_error",
- "deno_path_util",
- "deno_semver",
- "indexmap 2.14.0",
- "serde",
- "serde_json",
- "sys_traits",
- "thiserror 2.0.18",
- "url",
-]
-
[[package]]
name = "deno_path_util"
-version = "0.3.1"
+version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c87b8996966ae1b13ee9c20219b1d10fc53905b9570faae6adfa34614fd15224"
+checksum = "516f813389095889776b81cc9108ff6f336fd9409b4b12fc0138aea23d2708e1"
dependencies = [
- "deno_error",
+ "deno_error 0.6.1",
"percent-encoding",
"sys_traits",
"thiserror 2.0.18",
@@ -4432,186 +3752,57 @@ dependencies = [
[[package]]
name = "deno_permissions"
-version = "0.49.0"
+version = "0.68.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "abf879dff0b3de4dbcb78d6dda3a55e711369d5b9f479270a82853ef106c4176"
+checksum = "baa14d9c3fbba59836ffbaac7f736a2f48d4a5fc209a2103d1e1b898915232a5"
dependencies = [
- "capacity_builder 0.5.0",
- "deno_core",
- "deno_error",
+ "capacity_builder",
+ "deno_error 0.6.1",
"deno_path_util",
"deno_terminal",
+ "deno_unsync",
"fqdn",
+ "ipnetwork",
"libc",
"log",
- "once_cell",
- "percent-encoding",
- "serde",
- "thiserror 2.0.18",
- "which 6.0.3",
- "winapi",
-]
-
-[[package]]
-name = "deno_process"
-version = "0.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "700f8a2c9d369e7035e693f26a671489a73724450cbbc0a1e32f3966ef2f21fb"
-dependencies = [
- "deno_core",
- "deno_error",
- "deno_fs",
- "deno_io",
- "deno_os",
- "deno_path_util",
- "deno_permissions",
- "libc",
- "log",
- "memchr",
"nix 0.27.1",
- "pin-project-lite",
- "rand 0.8.5",
- "serde",
- "simd-json",
- "tempfile",
- "thiserror 2.0.18",
- "tokio",
- "which 6.0.3",
- "winapi",
- "windows-sys 0.59.0",
-]
-
-[[package]]
-name = "deno_resolver"
-version = "0.21.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "93c4ceec7b6e22344047b8a5577bb8239dc0a99884c25c1fa7d8611f4c3ed28b"
-dependencies = [
- "anyhow",
- "async-once-cell",
- "async-trait",
- "base32",
- "boxed_error",
- "dashmap 5.5.3",
- "deno_cache_dir",
- "deno_config",
- "deno_error",
- "deno_media_type",
- "deno_npm",
- "deno_package_json",
- "deno_path_util",
- "deno_semver",
- "deno_terminal",
- "futures",
- "log",
- "node_resolver",
"once_cell",
"parking_lot",
+ "percent-encoding",
+ "serde",
+ "serde_json",
"sys_traits",
+ "temp_deno_which",
"thiserror 2.0.18",
"url",
-]
-
-[[package]]
-name = "deno_runtime"
-version = "0.198.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26a54d54ca920e5256c1e910c7574787d009a34afd41b20ad250278bb1aea290"
-dependencies = [
- "color-print",
- "deno_ast",
- "deno_broadcast_channel",
- "deno_cache",
- "deno_canvas",
- "deno_console",
- "deno_core",
- "deno_cron",
- "deno_crypto",
- "deno_error",
- "deno_fetch",
- "deno_ffi",
- "deno_fs",
- "deno_http",
- "deno_io",
- "deno_kv",
- "deno_napi",
- "deno_net",
- "deno_node",
- "deno_os",
- "deno_path_util",
- "deno_permissions",
- "deno_process",
- "deno_resolver",
- "deno_telemetry",
- "deno_terminal",
- "deno_tls",
- "deno_url",
- "deno_web",
- "deno_webgpu",
- "deno_webidl",
- "deno_websocket",
- "deno_webstorage",
- "dlopen2 0.6.1",
- "encoding_rs",
- "fastwebsockets",
- "http 1.4.0",
- "http-body-util",
- "hyper 0.14.32",
- "hyper 1.9.0",
- "hyper-util",
- "libc",
- "log",
- "nix 0.27.1",
- "node_resolver",
- "notify",
- "ntapi",
- "once_cell",
- "percent-encoding",
- "regex",
- "rustyline",
- "same-file",
- "serde",
- "sys_traits",
- "tempfile",
- "thiserror 2.0.18",
- "tokio",
- "tokio-metrics",
- "twox-hash 1.6.3",
- "uuid",
- "which 6.0.3",
"winapi",
"windows-sys 0.59.0",
]
[[package]]
-name = "deno_semver"
-version = "0.7.1"
+name = "deno_subprocess_windows"
+version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4775271f9b5602482698f76d24ea9ed8ba27af7f587a7e9a876916300c542435"
+checksum = "7bc6b10059f0ccb14c6e0319c5275f0407fb2f9ffe405cd555700561999ea4bf"
dependencies = [
- "capacity_builder 0.5.0",
- "deno_error",
- "ecow",
- "hipstr",
- "monch",
- "once_cell",
- "serde",
- "thiserror 2.0.18",
- "url",
+ "fastrand",
+ "futures-channel",
+ "libc",
+ "windows-sys 0.59.0",
]
[[package]]
name = "deno_telemetry"
-version = "0.12.0"
+version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d73802ee27361bbb6c0e3c04a799b39f458afbed1972c4aff0867420d1c36fdb"
+checksum = "377581966bd34e85ce230f7f4b1dee7e16c6bf7f845b4690648507ce7ba105d9"
dependencies = [
"async-trait",
"deno_core",
- "deno_error",
+ "deno_error 0.6.1",
"deno_tls",
"http-body-util",
- "hyper 1.9.0",
+ "hyper 1.10.1",
"hyper-rustls 0.27.9",
"hyper-util",
"log",
@@ -4629,9 +3820,9 @@ dependencies = [
[[package]]
name = "deno_terminal"
-version = "0.2.3"
+version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f3ba8041ae7319b3ca6a64c399df4112badcbbe0868b4517637647614bede4be"
+checksum = "23f71c27009e0141dedd315f1dfa3ebb0a6ca4acce7c080fac576ea415a465f6"
dependencies = [
"once_cell",
"termcolor",
@@ -4639,12 +3830,12 @@ dependencies = [
[[package]]
name = "deno_tls"
-version = "0.177.0"
+version = "0.196.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1e3ceb2be448150d8214e8fc454c947e0ea94f6ce16556544f05a67ad5a16b8"
+checksum = "a7835eb6a8d114703b0293573fbb8885bc2c81e8c35363b9e89391dd3c541fa6"
dependencies = [
"deno_core",
- "deno_error",
+ "deno_error 0.6.1",
"deno_native_certs",
"rustls 0.23.35",
"rustls-pemfile 2.2.0",
@@ -4669,27 +3860,26 @@ dependencies = [
[[package]]
name = "deno_url"
-version = "0.190.0"
+version = "0.209.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d79e743ad841f7826d46c6944580f5ba665fe9ab4c31a68c4eed8b5a78225da3"
+checksum = "8cccbb10fb59f29b04161055a65f7fe83364f9e8317d4d2ce3bd7979faf63a87"
dependencies = [
"deno_core",
- "deno_error",
- "thiserror 2.0.18",
+ "deno_error 0.6.1",
"urlpattern",
]
[[package]]
name = "deno_web"
-version = "0.221.0"
+version = "0.240.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8041ba73bb2f238c61b5e4ed341d2fe1f9464a71115a240ab3390480b3c10e12"
+checksum = "1d6b8a97cc90b6aaea20fe200cfa7e5b6953dd33ecda0af50d6d360387c33df4"
dependencies = [
"async-trait",
- "base64-simd 0.8.0",
+ "base64-simd",
"bytes",
"deno_core",
- "deno_error",
+ "deno_error 0.6.1",
"deno_permissions",
"encoding_rs",
"flate2",
@@ -4700,197 +3890,40 @@ dependencies = [
"uuid",
]
-[[package]]
-name = "deno_webgpu"
-version = "0.157.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4077584c0ccfde0737e576c396bbf1645f25ed0ebf4f44543e0ad13729285cf3"
-dependencies = [
- "deno_core",
- "deno_error",
- "raw-window-handle",
- "serde",
- "thiserror 2.0.18",
- "tokio",
- "wgpu-core",
- "wgpu-types",
-]
-
[[package]]
name = "deno_webidl"
-version = "0.190.0"
+version = "0.209.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4ff81a990196bf3a80fe5d339b4eb8b411ef17634d60d399a63bae6e71a37c9"
+checksum = "a20bbfac0cf15918f7cbd0b55c366ac20ad65c825f362fa684c3104984254d3b"
dependencies = [
"deno_core",
]
-[[package]]
-name = "deno_websocket"
-version = "0.195.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2ad15c3856dd1748f9a36102e90b4e345e40d7d64a9bf6672d584201e1fded28"
-dependencies = [
- "bytes",
- "deno_core",
- "deno_error",
- "deno_net",
- "deno_permissions",
- "deno_tls",
- "fastwebsockets",
- "h2 0.4.13",
- "http 1.4.0",
- "http-body-util",
- "hyper 1.9.0",
- "hyper-util",
- "once_cell",
- "rustls-tokio-stream",
- "serde",
- "thiserror 2.0.18",
- "tokio",
-]
-
-[[package]]
-name = "deno_webstorage"
-version = "0.185.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "079dc4f6ce91f53bb848bad8d743dc20d16ca44cbed3425531cf5d922b1a45bc"
-dependencies = [
- "deno_core",
- "deno_error",
- "deno_web",
- "rusqlite",
- "thiserror 2.0.18",
-]
-
-[[package]]
-name = "deno_whoami"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e75e4caa92b98a27f09c671d1399aee0f5970aa491b9a598523aac000a2192e3"
-dependencies = [
- "libc",
- "whoami",
-]
-
-[[package]]
-name = "denokv_proto"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d5b77de4d3b9215e14624d4f4eb16cb38c0810e3f5860ba3b3fc47d0537f9a4d"
-dependencies = [
- "async-trait",
- "chrono",
- "deno_error",
- "futures",
- "num-bigint",
- "prost",
- "serde",
- "uuid",
-]
-
-[[package]]
-name = "denokv_remote"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c6497c28eec268ed99f1e8664f0842935f02d1508529c67d94c57ca5d893d743"
-dependencies = [
- "async-stream",
- "async-trait",
- "bytes",
- "chrono",
- "deno_error",
- "denokv_proto",
- "futures",
- "http 1.4.0",
- "log",
- "prost",
- "rand 0.8.5",
- "serde",
- "serde_json",
- "thiserror 2.0.18",
- "tokio",
- "tokio-util",
- "url",
- "uuid",
-]
-
-[[package]]
-name = "denokv_sqlite"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc0f21a450a35eb85760761401fddf9bfff9840127be07a6ca5c31863127913d"
-dependencies = [
- "async-stream",
- "async-trait",
- "chrono",
- "deno_error",
- "denokv_proto",
- "futures",
- "hex",
- "log",
- "num-bigint",
- "rand 0.8.5",
- "rusqlite",
- "serde_json",
- "thiserror 2.0.18",
- "tokio",
- "tokio-stream",
- "uuid",
- "v8_valueserializer",
-]
-
[[package]]
name = "der"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
dependencies = [
- "const-oid 0.9.6",
- "der_derive",
+ "const-oid",
"pem-rfc7468",
"zeroize",
]
-[[package]]
-name = "der-parser"
-version = "8.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dbd676fbbab537128ef0278adb5576cf363cff6aa22a7b24effe97347cfab61e"
-dependencies = [
- "asn1-rs 0.5.2",
- "displaydoc",
- "nom 7.1.3",
- "num-bigint",
- "num-traits",
- "rusticata-macros",
-]
-
[[package]]
name = "der-parser"
version = "9.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553"
dependencies = [
- "asn1-rs 0.6.2",
+ "asn1-rs",
"displaydoc",
- "nom 7.1.3",
+ "nom",
"num-bigint",
"num-traits",
"rusticata-macros",
]
-[[package]]
-name = "der_derive"
-version = "0.7.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.117",
-]
-
[[package]]
name = "deranged"
version = "0.5.8"
@@ -5032,23 +4065,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer 0.10.4",
- "const-oid 0.9.6",
- "crypto-common 0.1.7",
+ "const-oid",
+ "crypto-common",
"subtle",
]
-[[package]]
-name = "digest"
-version = "0.11.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c"
-dependencies = [
- "block-buffer 0.12.0",
- "const-oid 0.10.2",
- "crypto-common 0.2.1",
- "ctutils",
-]
-
[[package]]
name = "dirs"
version = "4.0.0"
@@ -5134,27 +4155,15 @@ dependencies = [
[[package]]
name = "displaydoc"
-version = "0.2.5"
+version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
+checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
]
-[[package]]
-name = "dlopen2"
-version = "0.6.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6bc2c7ed06fd72a8513ded8d0d2f6fd2655a85d6885c48cae8625d80faf28c03"
-dependencies = [
- "dlopen2_derive",
- "libc",
- "once_cell",
- "winapi",
-]
-
[[package]]
name = "dlopen2"
version = "0.7.0"
@@ -5178,15 +4187,6 @@ dependencies = [
"syn 2.0.117",
]
-[[package]]
-name = "document-features"
-version = "0.2.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61"
-dependencies = [
- "litrs",
-]
-
[[package]]
name = "dotenv"
version = "0.15.0"
@@ -5207,35 +4207,20 @@ checksum = "117240f60069e65410b3ae1bb213295bd828f707b5bec6596a1afc8793ce0cbc"
[[package]]
name = "dprint-swc-ext"
-version = "0.20.0"
+version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ba28c12892aadb751c2ba7001d8460faee4748a04b4edc51c7121cc67ee03db"
+checksum = "cf592ae6a864437e98ef9c6ae7936b822077e9d038a3a48ee081ab92313afad4"
dependencies = [
"num-bigint",
- "rustc-hash 1.1.0",
+ "rustc-hash 2.1.2",
"swc_atoms",
"swc_common",
"swc_ecma_ast",
+ "swc_ecma_lexer",
"swc_ecma_parser",
"text_lines",
]
-[[package]]
-name = "dsa"
-version = "0.6.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "48bc224a9084ad760195584ce5abb3c2c34a225fa312a128ad245a6b412b7689"
-dependencies = [
- "digest 0.10.7",
- "num-bigint-dig",
- "num-traits",
- "pkcs8",
- "rfc6979",
- "sha2 0.10.9",
- "signature",
- "zeroize",
-]
-
[[package]]
name = "duct"
version = "0.13.7"
@@ -5276,41 +4261,6 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1d926b4d407d372f141f93bb444696142c29d32962ccbd3531117cf3aa0bfa9"
-[[package]]
-name = "dynasm"
-version = "1.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "add9a102807b524ec050363f09e06f1504214b0e1c7797f64261c891022dce8b"
-dependencies = [
- "bitflags 1.3.2",
- "byteorder",
- "lazy_static",
- "proc-macro-error",
- "proc-macro2",
- "quote",
- "syn 1.0.109",
-]
-
-[[package]]
-name = "dynasmrt"
-version = "1.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64fba5a42bd76a17cad4bfa00de168ee1cbfa06a5e8ce992ae880218c05641a9"
-dependencies = [
- "byteorder",
- "dynasm",
- "memmap2 0.5.10",
-]
-
-[[package]]
-name = "ecb"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a8bfa975b1aec2145850fcaa1c6fe269a16578c44705a532ae3edc92b8881c7"
-dependencies = [
- "cipher 0.4.4",
-]
-
[[package]]
name = "ecdsa"
version = "0.16.9"
@@ -5325,15 +4275,6 @@ dependencies = [
"spki",
]
-[[package]]
-name = "ecow"
-version = "0.2.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "78e4f79b296fbaab6ce2e22d52cb4c7f010fe0ebe7a32e34fa25885fd797bd02"
-dependencies = [
- "serde",
-]
-
[[package]]
name = "ed25519"
version = "2.2.3"
@@ -5360,18 +4301,6 @@ dependencies = [
"zeroize",
]
-[[package]]
-name = "ed448-goldilocks"
-version = "0.8.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "06924531e9e90130842b012e447f85bdaf9161bc8a0f8092be8cb70b01ebe092"
-dependencies = [
- "fiat-crypto 0.1.20",
- "hex",
- "subtle",
- "zeroize",
-]
-
[[package]]
name = "educe"
version = "0.6.0"
@@ -5386,9 +4315,9 @@ dependencies = [
[[package]]
name = "either"
-version = "1.15.0"
+version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e"
dependencies = [
"serde",
]
@@ -5400,7 +4329,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
dependencies = [
"base16ct",
- "base64ct",
"crypto-bigint",
"digest 0.10.7",
"ff",
@@ -5411,8 +4339,6 @@ dependencies = [
"pkcs8",
"rand_core 0.6.4",
"sec1",
- "serde_json",
- "serdect",
"subtle",
"zeroize",
]
@@ -5425,26 +4351,20 @@ checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
[[package]]
name = "encoding_rs"
-version = "0.8.33"
+version = "0.8.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1"
+checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
dependencies = [
"cfg-if",
]
-[[package]]
-name = "endian-type"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
-
[[package]]
name = "enum-as-inner"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc"
dependencies = [
- "heck 0.5.0",
+ "heck",
"proc-macro2",
"quote",
"syn 2.0.117",
@@ -5517,12 +4437,6 @@ dependencies = [
"windows-sys 0.61.2",
]
-[[package]]
-name = "error-code"
-version = "3.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59"
-
[[package]]
name = "error_reporter"
version = "1.0.0"
@@ -5583,7 +4497,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74fef4569247a5f429d9156b9d0a2599914385dd189c539334c625d8099d90ab"
dependencies = [
"futures-core",
- "nom 7.1.3",
+ "nom",
"pin-project-lite",
]
@@ -5593,25 +4507,13 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
-[[package]]
-name = "fallible-iterator"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
-
-[[package]]
-name = "fallible-streaming-iterator"
-version = "0.1.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
-
[[package]]
name = "fancy-regex"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298"
dependencies = [
- "bit-set 0.8.0",
+ "bit-set",
"regex-automata",
"regex-syntax 0.8.10",
]
@@ -5622,7 +4524,7 @@ version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72cf461f865c862bb7dc573f643dd6a2b6842f7c30b07882b56bd148cc2761b8"
dependencies = [
- "bit-set 0.8.0",
+ "bit-set",
"regex-automata",
"regex-syntax 0.8.10",
]
@@ -5633,61 +4535,12 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9afc2bd4d5a73106dd53d10d73d3401c2f32730ba2c0b93ddb888a8983680471"
-[[package]]
-name = "faster-hex"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2a2b11eda1d40935b26cf18f6833c526845ae8c41e58d09af6adeb6f0269183"
-dependencies = [
- "serde",
-]
-
[[package]]
name = "fastrand"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6"
-[[package]]
-name = "fastwebsockets"
-version = "0.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9dac026e15fb7e44d768880b868a0fd5bd30ffdee272e88b3060f657a5a72947"
-dependencies = [
- "base64 0.21.7",
- "bytes",
- "http-body-util",
- "hyper 1.9.0",
- "hyper-util",
- "pin-project",
- "rand 0.8.5",
- "sha1",
- "simdutf8",
- "thiserror 1.0.69",
- "tokio",
- "utf-8",
-]
-
-[[package]]
-name = "fd-lock"
-version = "4.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78"
-dependencies = [
- "cfg-if",
- "rustix 1.1.4",
- "windows-sys 0.59.0",
-]
-
-[[package]]
-name = "fdeflate"
-version = "0.3.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
-dependencies = [
- "simd-adler32",
-]
-
[[package]]
name = "ff"
version = "0.13.1"
@@ -5698,12 +4551,6 @@ dependencies = [
"subtle",
]
-[[package]]
-name = "fiat-crypto"
-version = "0.1.20"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77"
-
[[package]]
name = "fiat-crypto"
version = "0.2.9"
@@ -5712,13 +4559,12 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
[[package]]
name = "filetime"
-version = "0.2.27"
+version = "0.2.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db"
+checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759"
dependencies = [
"cfg-if",
"libc",
- "libredox",
]
[[package]]
@@ -5739,7 +4585,7 @@ version = "25.12.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35f6839d7b3b98adde531effaf34f0c2badc6f4735d26fe74709d8e513a96ef3"
dependencies = [
- "bitflags 2.9.4",
+ "bitflags 2.11.1",
"rustc_version 0.4.1",
]
@@ -5751,19 +4597,10 @@ checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
dependencies = [
"crc32fast",
"libz-sys",
- "miniz_oxide 0.8.9",
+ "miniz_oxide",
"zlib-rs",
]
-[[package]]
-name = "float-cmp"
-version = "0.10.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8"
-dependencies = [
- "num-traits",
-]
-
[[package]]
name = "float8"
version = "0.6.1"
@@ -5812,28 +4649,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
- "foreign-types-shared 0.1.1",
-]
-
-[[package]]
-name = "foreign-types"
-version = "0.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
-dependencies = [
- "foreign-types-macros",
- "foreign-types-shared 0.3.1",
-]
-
-[[package]]
-name = "foreign-types-macros"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.117",
+ "foreign-types-shared",
]
[[package]]
@@ -5842,12 +4658,6 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
-[[package]]
-name = "foreign-types-shared"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
-
[[package]]
name = "form_urlencoded"
version = "1.2.2"
@@ -5865,11 +4675,10 @@ checksum = "eb540cf7bc4fe6df9d8f7f0c974cfd0dce8ed4e9e8884e73433b503ee78b4e7d"
[[package]]
name = "from_variant"
-version = "0.1.9"
+version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32016f1242eb82af5474752d00fd8ebcd9004bd69b462b1c91de833972d08ed4"
+checksum = "308530a56b099da144ebc5d8e179f343ad928fa2b3558d1eb3db9af18d6eff43"
dependencies = [
- "proc-macro2",
"swc_macros_common",
"syn 2.0.117",
]
@@ -5901,15 +4710,6 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
-[[package]]
-name = "fsevent-sys"
-version = "4.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2"
-dependencies = [
- "libc",
-]
-
[[package]]
name = "fslock"
version = "0.2.1"
@@ -6315,17 +5115,6 @@ dependencies = [
"syn 2.0.117",
]
-[[package]]
-name = "gl_generator"
-version = "0.14.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d"
-dependencies = [
- "khronos_api",
- "log",
- "xml-rs",
-]
-
[[package]]
name = "glob"
version = "0.3.3"
@@ -6357,27 +5146,6 @@ dependencies = [
"wasm-bindgen",
]
-[[package]]
-name = "glow"
-version = "0.13.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bd348e04c43b32574f2de31c8bb397d96c9fcfa1371bd4ca6d8bdc464ab121b1"
-dependencies = [
- "js-sys",
- "slotmap",
- "wasm-bindgen",
- "web-sys",
-]
-
-[[package]]
-name = "glutin_wgl_sys"
-version = "0.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6c8098adac955faa2d31079b65dc48841251f69efd3ac25477903fc424362ead"
-dependencies = [
- "gl_generator",
-]
-
[[package]]
name = "google-cloud-auth"
version = "0.17.2"
@@ -6407,7 +5175,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de13e62d7e0ffc3eb40a0113ddf753cf6ec741be739164442b08893db4f9bfca"
dependencies = [
"google-cloud-token",
- "http 1.4.0",
+ "http 1.4.1",
"thiserror 1.0.69",
"tokio",
"tokio-retry2",
@@ -6468,55 +5236,16 @@ dependencies = [
[[package]]
name = "gosyn"
-version = "0.2.9"
+version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5eb37859fda6792e95231aef1c5838f4043ec0ee352d8313421e311c606df612"
+checksum = "c99c1502d84229dc7ddb6af755f40ebe80e7e932fa78ecef979cedcf9999ba93"
dependencies = [
"anyhow",
- "strum 0.25.0",
- "thiserror 1.0.69",
+ "strum",
+ "thiserror 2.0.18",
"unic-ucd-category",
]
-[[package]]
-name = "gpu-alloc"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171"
-dependencies = [
- "bitflags 2.9.4",
- "gpu-alloc-types",
-]
-
-[[package]]
-name = "gpu-alloc-types"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4"
-dependencies = [
- "bitflags 2.9.4",
-]
-
-[[package]]
-name = "gpu-descriptor"
-version = "0.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca"
-dependencies = [
- "bitflags 2.9.4",
- "gpu-descriptor-types",
- "hashbrown 0.15.5",
-]
-
-[[package]]
-name = "gpu-descriptor-types"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91"
-dependencies = [
- "bitflags 2.9.4",
-]
-
[[package]]
name = "group"
version = "0.13.0"
@@ -6558,16 +5287,16 @@ dependencies = [
[[package]]
name = "h2"
-version = "0.4.13"
+version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54"
+checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733"
dependencies = [
"atomic-waker",
"bytes",
"fnv",
"futures-core",
"futures-sink",
- "http 1.4.0",
+ "http 1.4.1",
"indexmap 2.14.0",
"slab",
"tokio",
@@ -6590,16 +5319,6 @@ dependencies = [
"zerocopy",
]
-[[package]]
-name = "halfbrown"
-version = "0.2.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8588661a8607108a5ca69cab034063441a0413a0b041c13618a7dd348021ef6f"
-dependencies = [
- "hashbrown 0.14.5",
- "serde",
-]
-
[[package]]
name = "hashbrown"
version = "0.12.3"
@@ -6632,21 +5351,27 @@ dependencies = [
[[package]]
name = "hashbrown"
-version = "0.16.0"
+version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
+checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
dependencies = [
"allocator-api2",
"equivalent",
"foldhash 0.2.0",
"serde",
+ "serde_core",
]
[[package]]
name = "hashbrown"
-version = "0.17.0"
+version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
+checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
+dependencies = [
+ "allocator-api2",
+ "equivalent",
+ "foldhash 0.2.0",
+]
[[package]]
name = "hashify"
@@ -6660,15 +5385,6 @@ dependencies = [
"syn 2.0.117",
]
-[[package]]
-name = "hashlink"
-version = "0.9.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af"
-dependencies = [
- "hashbrown 0.14.5",
-]
-
[[package]]
name = "hashlink"
version = "0.10.0"
@@ -6678,20 +5394,6 @@ dependencies = [
"hashbrown 0.15.5",
]
-[[package]]
-name = "hdrhistogram"
-version = "7.5.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d"
-dependencies = [
- "base64 0.21.7",
- "byteorder",
- "crossbeam-channel",
- "flate2",
- "nom 7.1.3",
- "num-traits",
-]
-
[[package]]
name = "headers"
version = "0.4.1"
@@ -6701,7 +5403,7 @@ dependencies = [
"base64 0.22.1",
"bytes",
"headers-core",
- "http 1.4.0",
+ "http 1.4.1",
"httpdate",
"mime",
"sha1",
@@ -6713,15 +5415,9 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4"
dependencies = [
- "http 1.4.0",
+ "http 1.4.1",
]
-[[package]]
-name = "heck"
-version = "0.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
-
[[package]]
name = "heck"
version = "0.5.0"
@@ -6740,12 +5436,6 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
-[[package]]
-name = "hexf-parse"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
-
[[package]]
name = "hf-hub"
version = "0.4.3"
@@ -6754,7 +5444,7 @@ checksum = "629d8f3bbeda9d148036d6b0de0a3ab947abd08ce90626327fc3547a49d59d97"
dependencies = [
"dirs 6.0.0",
"futures",
- "http 1.4.0",
+ "http 1.4.1",
"indicatif",
"libc",
"log",
@@ -6818,24 +5508,13 @@ dependencies = [
"tracing",
]
-[[package]]
-name = "hipstr"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97971ffc85d4c98de12e2608e992a43f5294ebb625fdb045b27c731b64c4c6d6"
-dependencies = [
- "serde",
- "serde_bytes",
- "sptr",
-]
-
[[package]]
name = "hkdf"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
dependencies = [
- "hmac 0.12.1",
+ "hmac",
]
[[package]]
@@ -6847,15 +5526,6 @@ dependencies = [
"digest 0.10.7",
]
-[[package]]
-name = "hmac"
-version = "0.13.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f"
-dependencies = [
- "digest 0.11.2",
-]
-
[[package]]
name = "home"
version = "0.5.12"
@@ -6878,15 +5548,14 @@ dependencies = [
[[package]]
name = "hstr"
-version = "0.2.17"
+version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1a26def229ea95a8709dad32868d975d0dd40235bd2ce82920e4a8fe692b5e0"
+checksum = "31f11d91d7befd2ffd9d216e9e5ea1fae6174b20a2a1b67a688138003d2f4122"
dependencies = [
"hashbrown 0.14.5",
"new_debug_unreachable",
"once_cell",
- "phf 0.11.3",
- "rustc-hash 1.1.0",
+ "rustc-hash 2.1.2",
"triomphe",
]
@@ -6909,9 +5578,9 @@ dependencies = [
[[package]]
name = "http"
-version = "1.4.0"
+version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a"
+checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0"
dependencies = [
"bytes",
"itoa",
@@ -6935,7 +5604,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
dependencies = [
"bytes",
- "http 1.4.0",
+ "http 1.4.1",
]
[[package]]
@@ -6946,7 +5615,7 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
dependencies = [
"bytes",
"futures-core",
- "http 1.4.0",
+ "http 1.4.1",
"http-body 1.0.1",
"pin-project-lite",
]
@@ -6972,9 +5641,9 @@ dependencies = [
"async-compression",
"bstr",
"futures",
- "http 1.4.0",
+ "http 1.4.1",
"http-body-util",
- "hyper 1.9.0",
+ "hyper 1.10.1",
"hyper-rustls 0.26.0",
"hyper-tls",
"hyper-tungstenite",
@@ -6998,15 +5667,6 @@ version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424"
-[[package]]
-name = "hybrid-array"
-version = "0.4.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08d46837a0ed51fe95bd3b05de33cd64a1ee88fc797477ca48446872504507c5"
-dependencies = [
- "typenum",
-]
-
[[package]]
name = "hyper"
version = "0.14.32"
@@ -7033,16 +5693,16 @@ dependencies = [
[[package]]
name = "hyper"
-version = "1.9.0"
+version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca"
+checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498"
dependencies = [
"atomic-waker",
"bytes",
"futures-channel",
"futures-core",
- "h2 0.4.13",
- "http 1.4.0",
+ "h2 0.4.14",
+ "http 1.4.1",
"http-body 1.0.1",
"httparse",
"httpdate",
@@ -7062,8 +5722,8 @@ dependencies = [
"bytes",
"futures-util",
"headers",
- "http 1.4.0",
- "hyper 1.9.0",
+ "http 1.4.1",
+ "hyper 1.10.1",
"hyper-rustls 0.27.9",
"hyper-tls",
"hyper-util",
@@ -7083,7 +5743,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73b7d8abf35697b81a825e386fc151e0d503e8cb5fcb93cc8669c376dfd6f278"
dependencies = [
"hex",
- "hyper 1.9.0",
+ "hyper 1.10.1",
"hyper-util",
"pin-project-lite",
"tokio",
@@ -7114,8 +5774,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c"
dependencies = [
"futures-util",
- "http 1.4.0",
- "hyper 1.9.0",
+ "http 1.4.1",
+ "hyper 1.10.1",
"hyper-util",
"log",
"rustls 0.22.4",
@@ -7132,8 +5792,8 @@ version = "0.27.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f"
dependencies = [
- "http 1.4.0",
- "hyper 1.9.0",
+ "http 1.4.1",
+ "hyper 1.10.1",
"hyper-util",
"log",
"rustls 0.23.35",
@@ -7150,7 +5810,7 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0"
dependencies = [
- "hyper 1.9.0",
+ "hyper 1.10.1",
"hyper-util",
"pin-project-lite",
"tokio",
@@ -7165,7 +5825,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
dependencies = [
"bytes",
"http-body-util",
- "hyper 1.9.0",
+ "hyper 1.10.1",
"hyper-util",
"native-tls",
"tokio",
@@ -7180,7 +5840,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a343d17fe7885302ed7252767dc7bb83609a874b6ff581142241ec4b73957ad"
dependencies = [
"http-body-util",
- "hyper 1.9.0",
+ "hyper 1.10.1",
"hyper-util",
"pin-project-lite",
"tokio",
@@ -7198,14 +5858,14 @@ dependencies = [
"bytes",
"futures-channel",
"futures-util",
- "http 1.4.0",
+ "http 1.4.1",
"http-body 1.0.1",
- "hyper 1.9.0",
+ "hyper 1.10.1",
"ipnet",
"libc",
"percent-encoding",
"pin-project-lite",
- "socket2 0.6.3",
+ "socket2 0.6.4",
"system-configuration",
"tokio",
"tower-service",
@@ -7221,7 +5881,7 @@ checksum = "986c5ce3b994526b3cd75578e62554abd09f0899d6206de48b3e96ab34ccc8c7"
dependencies = [
"hex",
"http-body-util",
- "hyper 1.9.0",
+ "hyper 1.10.1",
"hyper-util",
"pin-project-lite",
"tokio",
@@ -7261,7 +5921,7 @@ dependencies = [
"displaydoc",
"potential_utf",
"utf8_iter",
- "yoke 0.8.2",
+ "yoke",
"zerofrom",
"zerovec",
]
@@ -7328,7 +5988,7 @@ dependencies = [
"displaydoc",
"icu_locale_core",
"writeable",
- "yoke 0.8.2",
+ "yoke",
"zerofrom",
"zerotrie",
"zerovec",
@@ -7373,52 +6033,6 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd62e6b5e86ea8eeeb8db1de02880a6abc01a397b2ebb64b5d74ac255318f5cb"
-[[package]]
-name = "ignore"
-version = "0.4.25"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a"
-dependencies = [
- "crossbeam-deque",
- "globset",
- "log",
- "memchr",
- "regex-automata",
- "same-file",
- "walkdir",
- "winapi-util",
-]
-
-[[package]]
-name = "image"
-version = "0.24.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d"
-dependencies = [
- "bytemuck",
- "byteorder",
- "color_quant",
- "num-traits",
- "png",
-]
-
-[[package]]
-name = "import_map"
-version = "0.21.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1215d4d92511fbbdaea50e750e91f2429598ef817f02b579158e92803b52c00a"
-dependencies = [
- "boxed_error",
- "deno_error",
- "indexmap 2.14.0",
- "log",
- "percent-encoding",
- "serde",
- "serde_json",
- "thiserror 2.0.18",
- "url",
-]
-
[[package]]
name = "indexmap"
version = "1.9.3"
@@ -7437,7 +6051,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
dependencies = [
"equivalent",
- "hashbrown 0.17.0",
+ "hashbrown 0.17.1",
"serde",
"serde_core",
]
@@ -7455,33 +6069,12 @@ dependencies = [
"web-time",
]
-[[package]]
-name = "inotify"
-version = "0.9.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff"
-dependencies = [
- "bitflags 1.3.2",
- "inotify-sys",
- "libc",
-]
-
-[[package]]
-name = "inotify-sys"
-version = "0.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
-dependencies = [
- "libc",
-]
-
[[package]]
name = "inout"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
dependencies = [
- "block-padding 0.3.3",
"generic-array",
]
@@ -7506,7 +6099,7 @@ version = "0.7.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d09b98f7eace8982db770e4408e7470b028ce513ac28fecdc6bf4c30fe92b62"
dependencies = [
- "bitflags 2.9.4",
+ "bitflags 2.11.1",
"cfg-if",
"libc",
]
@@ -7517,7 +6110,7 @@ version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222"
dependencies = [
- "socket2 0.6.3",
+ "socket2 0.6.4",
"widestring",
"windows-registry",
"windows-result 0.4.1",
@@ -7539,23 +6132,13 @@ dependencies = [
"serde",
]
-[[package]]
-name = "iri-string"
-version = "0.7.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20"
-dependencies = [
- "memchr",
- "serde",
-]
-
[[package]]
name = "is-macro"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d57a3e447e24c22647738e4607f1df1e0ec6f72e16182c4cd199f647cdfb0e4"
dependencies = [
- "heck 0.5.0",
+ "heck",
"proc-macro2",
"quote",
"syn 2.0.117",
@@ -7691,15 +6274,6 @@ dependencies = [
"thiserror 2.0.18",
]
-[[package]]
-name = "jsonc-parser"
-version = "0.26.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d6d80e6d70e7911a29f3cf3f44f452df85d06f73572b494ca99a2cad3fcf8f4"
-dependencies = [
- "serde_json",
-]
-
[[package]]
name = "jsonpath-rust"
version = "0.7.5"
@@ -7762,20 +6336,6 @@ dependencies = [
"windows-sys 0.52.0",
]
-[[package]]
-name = "k256"
-version = "0.13.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b"
-dependencies = [
- "cfg-if",
- "ecdsa",
- "elliptic-curve",
- "once_cell",
- "sha2 0.10.9",
- "signature",
-]
-
[[package]]
name = "k8s-openapi"
version = "0.25.0"
@@ -7788,15 +6348,6 @@ dependencies = [
"serde_json",
]
-[[package]]
-name = "keccak"
-version = "0.1.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653"
-dependencies = [
- "cpufeatures 0.2.17",
-]
-
[[package]]
name = "keyed_priority_queue"
version = "0.4.2"
@@ -7806,23 +6357,6 @@ dependencies = [
"indexmap 2.14.0",
]
-[[package]]
-name = "khronos-egl"
-version = "6.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76"
-dependencies = [
- "libc",
- "libloading 0.8.9",
- "pkg-config",
-]
-
-[[package]]
-name = "khronos_api"
-version = "3.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
-
[[package]]
name = "konst"
version = "0.2.20"
@@ -7838,26 +6372,6 @@ version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37"
-[[package]]
-name = "kqueue"
-version = "1.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a"
-dependencies = [
- "kqueue-sys",
- "libc",
-]
-
-[[package]]
-name = "kqueue-sys"
-version = "1.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b"
-dependencies = [
- "bitflags 1.3.2",
- "libc",
-]
-
[[package]]
name = "kube"
version = "1.1.0"
@@ -7883,10 +6397,10 @@ dependencies = [
"either",
"futures",
"home",
- "http 1.4.0",
+ "http 1.4.1",
"http-body 1.0.1",
"http-body-util",
- "hyper 1.9.0",
+ "hyper 1.10.1",
"hyper-http-proxy",
"hyper-rustls 0.27.9",
"hyper-timeout",
@@ -7917,7 +6431,7 @@ dependencies = [
"chrono",
"derive_more 2.1.1",
"form_urlencoded",
- "http 1.4.0",
+ "http 1.4.1",
"json-patch",
"k8s-openapi",
"schemars 0.8.22",
@@ -7974,29 +6488,6 @@ version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553"
-[[package]]
-name = "lazy-regex"
-version = "3.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6bae91019476d3ec7147de9aa291cadb6d870abf2f3015d2da73a90325ac1496"
-dependencies = [
- "lazy-regex-proc_macros",
- "once_cell",
- "regex",
-]
-
-[[package]]
-name = "lazy-regex-proc_macros"
-version = "3.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4de9c1e1439d8b7b3061b2d209809f447ca33241733d9a3c01eabf2dc8d94358"
-dependencies = [
- "proc-macro2",
- "quote",
- "regex",
- "syn 2.0.117",
-]
-
[[package]]
name = "lazy_static"
version = "1.5.0"
@@ -8081,16 +6572,6 @@ version = "0.2.186"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
-[[package]]
-name = "libffi"
-version = "3.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ce826c243048e3d5cec441799724de52e2d42f820468431fc3fceee2341871e2"
-dependencies = [
- "libc",
- "libffi-sys",
-]
-
[[package]]
name = "libffi-sys"
version = "2.3.0"
@@ -8106,7 +6587,7 @@ version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e668df13f2e97f3eed52d9301f6b1c4c1ccfccc30eab9e6628e4a8c1fc3546"
dependencies = [
- "bitflags 2.9.4",
+ "bitflags 2.11.1",
"bytes",
"lazy_static",
"libgssapi-sys",
@@ -8114,24 +6595,14 @@ dependencies = [
[[package]]
name = "libgssapi-sys"
-version = "0.3.3"
+version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7518e6902e94f92e7c7271232684b60988b4bd813529b4ef9d97aead96956ae8"
+checksum = "5103ac4557eacd36ff678b654b943f8966d3db9688fbd180a0b4c5464759ce17"
dependencies = [
"bindgen 0.71.1",
"pkg-config",
]
-[[package]]
-name = "libloading"
-version = "0.7.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
-dependencies = [
- "cfg-if",
- "winapi",
-]
-
[[package]]
name = "libloading"
version = "0.8.9"
@@ -8161,14 +6632,14 @@ dependencies = [
[[package]]
name = "libredox"
-version = "0.1.16"
+version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c"
+checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3"
dependencies = [
- "bitflags 2.9.4",
+ "bitflags 2.11.1",
"libc",
"plain",
- "redox_syscall 0.7.4",
+ "redox_syscall 0.8.0",
]
[[package]]
@@ -8177,7 +6648,6 @@ version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149"
dependencies = [
- "cc",
"pkg-config",
"vcpkg",
]
@@ -8205,9 +6675,9 @@ dependencies = [
[[package]]
name = "libz-sys"
-version = "1.1.28"
+version = "1.1.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fc3a226e576f50782b3305c5ccf458698f92798987f551c6a02efe8276721e22"
+checksum = "85bc9657773828b90eeb625adff10eeac83cc21bbfd8e23a03eaa8a33c9e28d9"
dependencies = [
"cc",
"libc",
@@ -8239,12 +6709,6 @@ version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
-[[package]]
-name = "litrs"
-version = "1.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
-
[[package]]
name = "lock_api"
version = "0.4.14"
@@ -8256,9 +6720,9 @@ dependencies = [
[[package]]
name = "log"
-version = "0.4.29"
+version = "0.4.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
+checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5"
[[package]]
name = "loom"
@@ -8291,7 +6755,16 @@ version = "0.16.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39"
dependencies = [
- "hashbrown 0.16.0",
+ "hashbrown 0.16.1",
+]
+
+[[package]]
+name = "lru"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a860605968fce16869fd239cf4237a82f3ac470723415db603b0e8b6c8d4fb9"
+dependencies = [
+ "hashbrown 0.17.1",
]
[[package]]
@@ -8315,14 +6788,14 @@ version = "0.11.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "373f5eceeeab7925e0c1098212f2fbc4d416adec9d35051a6ab251e824c1854a"
dependencies = [
- "twox-hash 2.1.2",
+ "twox-hash",
]
[[package]]
name = "lz4_flex"
-version = "0.13.0"
+version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "db9a0d582c2874f68138a16ce1867e0ffde6c0bb0a0df85e1f36d04146db488a"
+checksum = "7ef0d4ed8669f8f8826eb00dc878084aa8f253506c4fd5e8f58f5bce72ddb97e"
[[package]]
name = "lzma-sys"
@@ -8472,15 +6945,6 @@ dependencies = [
"malachite-nz",
]
-[[package]]
-name = "malloc_buf"
-version = "0.0.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
-dependencies = [
- "libc",
-]
-
[[package]]
name = "mappable-rc"
version = "0.1.1"
@@ -8535,25 +6999,6 @@ dependencies = [
"digest 0.10.7",
]
-[[package]]
-name = "md-5"
-version = "0.11.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98"
-dependencies = [
- "cfg-if",
- "digest 0.11.2",
-]
-
-[[package]]
-name = "md4"
-version = "0.10.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7da5ac363534dce5fabf69949225e174fbf111a498bf0ff794c8ea1fba9f3dda"
-dependencies = [
- "digest 0.10.7",
-]
-
[[package]]
name = "md5"
version = "0.6.1"
@@ -8571,18 +7016,9 @@ dependencies = [
[[package]]
name = "memchr"
-version = "2.8.0"
+version = "2.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
-
-[[package]]
-name = "memmap2"
-version = "0.5.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327"
-dependencies = [
- "libc",
-]
+checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8"
[[package]]
name = "memmap2"
@@ -8594,12 +7030,6 @@ dependencies = [
"stable_deref_trait",
]
-[[package]]
-name = "memmem"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a64a92489e2744ce060c349162be1c5f33c6969234104dbd99ddb5feb08b8c15"
-
[[package]]
name = "memoffset"
version = "0.9.1"
@@ -8609,21 +7039,6 @@ dependencies = [
"autocfg",
]
-[[package]]
-name = "metal"
-version = "0.28.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5637e166ea14be6063a3f8ba5ccb9a4159df7d8f6d61c02fc3d480b1f90dcfcb"
-dependencies = [
- "bitflags 2.9.4",
- "block",
- "core-graphics-types",
- "foreign-types 0.5.0",
- "log",
- "objc",
- "paste",
-]
-
[[package]]
name = "miette"
version = "7.6.0"
@@ -8684,15 +7099,6 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
-[[package]]
-name = "miniz_oxide"
-version = "0.7.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08"
-dependencies = [
- "adler",
-]
-
[[package]]
name = "miniz_oxide"
version = "0.8.9"
@@ -8705,21 +7111,9 @@ dependencies = [
[[package]]
name = "mio"
-version = "0.8.11"
+version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
-dependencies = [
- "libc",
- "log",
- "wasi 0.11.1+wasi-snapshot-preview1",
- "windows-sys 0.48.0",
-]
-
-[[package]]
-name = "mio"
-version = "1.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1"
+checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda"
dependencies = [
"libc",
"wasi 0.11.1+wasi-snapshot-preview1",
@@ -8746,12 +7140,6 @@ dependencies = [
"uuid",
]
-[[package]]
-name = "monch"
-version = "0.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b52c1b33ff98142aecea13138bd399b68aa7ab5d9546c300988c345004001eea"
-
[[package]]
name = "monostate"
version = "0.1.18"
@@ -8783,7 +7171,7 @@ dependencies = [
"bytes",
"encoding_rs",
"futures-util",
- "http 1.4.0",
+ "http 1.4.1",
"httparse",
"memchr",
"mime",
@@ -8791,12 +7179,6 @@ dependencies = [
"version_check",
]
-[[package]]
-name = "multimap"
-version = "0.10.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084"
-
[[package]]
name = "murmurhash32"
version = "0.3.1"
@@ -8810,7 +7192,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66f62cad7623a9cb6f8f64037f0c4f69c8db8e82914334a83c9788201c2c1bfa"
dependencies = [
"darling 0.20.11",
- "heck 0.5.0",
+ "heck",
"num-bigint",
"proc-macro-crate",
"proc-macro-error2",
@@ -8823,42 +7205,42 @@ dependencies = [
[[package]]
name = "mysql_async"
-version = "0.36.2"
+version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d1d9585dc9058886ff3a1f48a23024dd1d054264dee7c5ae0e4bd640c953bee5"
+checksum = "3519e91b0d254ac1ffa495bc42053286cb2172ad7241d5b3b1b9f8a891f21ee2"
dependencies = [
"bytes",
"crossbeam-queue",
+ "crossbeam-utils",
"flate2",
"futures-core",
"futures-sink",
"futures-util",
"keyed_priority_queue",
- "lru 0.16.4",
+ "lru 0.18.0",
"mysql_common",
"native-tls",
"pem 3.0.6",
"percent-encoding",
- "rand 0.9.0",
+ "rand 0.10.1",
"serde",
- "serde_json",
- "socket2 0.5.10",
+ "socket2 0.6.4",
"thiserror 2.0.18",
"tokio",
"tokio-native-tls",
"tokio-util",
- "twox-hash 2.1.2",
+ "twox-hash",
"url",
]
[[package]]
name = "mysql_common"
-version = "0.35.5"
+version = "0.37.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fbb9f371618ce723f095c61fbcdc36e8936956d2b62832f9c7648689b338e052"
+checksum = "4b42ced54aa8ac97226486337973f9bc3956e24f03a23e88a6e18f640959d6e2"
dependencies = [
"base64 0.22.1",
- "bitflags 2.9.4",
+ "bitflags 2.11.1",
"btoi",
"byteorder",
"bytes",
@@ -8879,28 +7261,6 @@ dependencies = [
"uuid",
]
-[[package]]
-name = "naga"
-version = "0.20.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e536ae46fcab0876853bd4a632ede5df4b1c2527a58f6c5a4150fe86be858231"
-dependencies = [
- "arrayvec",
- "bit-set 0.5.3",
- "bitflags 2.9.4",
- "codespan-reporting",
- "hexf-parse",
- "indexmap 2.14.0",
- "log",
- "num-traits",
- "rustc-hash 1.1.0",
- "serde",
- "spirv",
- "termcolor",
- "thiserror 1.0.69",
- "unicode-xid",
-]
-
[[package]]
name = "nanorand"
version = "0.7.0"
@@ -8910,18 +7270,6 @@ dependencies = [
"getrandom 0.2.17",
]
-[[package]]
-name = "napi_sym"
-version = "0.120.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "33a55ec137cebb7f4a594edd16157a5b9d9addf7ebd29c88198ec4e0cff2e93e"
-dependencies = [
- "quote",
- "serde",
- "serde_json",
- "syn 2.0.117",
-]
-
[[package]]
name = "native-tls"
version = "0.2.16"
@@ -8934,52 +7282,24 @@ dependencies = [
"openssl-probe 0.2.1",
"openssl-sys",
"schannel",
- "security-framework 3.6.0",
+ "security-framework 3.7.0",
"security-framework-sys",
"tempfile",
]
-[[package]]
-name = "ndk-sys"
-version = "0.5.0+25.2.9519653"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691"
-dependencies = [
- "jni-sys 0.3.1",
-]
-
-[[package]]
-name = "netif"
-version = "0.1.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d29a01b9f018d6b7b277fef6c79fdbd9bf17bb2d1e298238055cafab49baa5ee"
-dependencies = [
- "libc",
- "winapi",
-]
-
[[package]]
name = "new_debug_unreachable"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
-[[package]]
-name = "nibble_vec"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43"
-dependencies = [
- "smallvec",
-]
-
[[package]]
name = "nix"
version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
dependencies = [
- "bitflags 2.9.4",
+ "bitflags 2.11.1",
"cfg-if",
"libc",
]
@@ -8990,9 +7310,9 @@ version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
dependencies = [
- "bitflags 2.9.4",
+ "bitflags 2.11.1",
"cfg-if",
- "cfg_aliases 0.2.1",
+ "cfg_aliases",
"libc",
]
@@ -9002,12 +7322,25 @@ version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
dependencies = [
- "bitflags 2.9.4",
+ "bitflags 2.11.1",
"cfg-if",
- "cfg_aliases 0.2.1",
+ "cfg_aliases",
"libc",
]
+[[package]]
+name = "nix"
+version = "0.31.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d"
+dependencies = [
+ "bitflags 2.11.1",
+ "cfg-if",
+ "cfg_aliases",
+ "libc",
+ "memoffset",
+]
+
[[package]]
name = "nkeys"
version = "0.4.5"
@@ -9023,42 +7356,6 @@ dependencies = [
"signatory",
]
-[[package]]
-name = "node_resolver"
-version = "0.28.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "808426e80ce77a311b24ac080caf18c23c632e035d797edb217ce74cdf6a0e71"
-dependencies = [
- "anyhow",
- "async-trait",
- "boxed_error",
- "dashmap 5.5.3",
- "deno_error",
- "deno_media_type",
- "deno_package_json",
- "deno_path_util",
- "futures",
- "lazy-regex",
- "once_cell",
- "path-clean",
- "regex",
- "serde",
- "serde_json",
- "sys_traits",
- "thiserror 2.0.18",
- "url",
-]
-
-[[package]]
-name = "nom"
-version = "5.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08959a387a676302eebf4ddbcbc611da04285579f76f88ee0506c63b1a61dd4b"
-dependencies = [
- "memchr",
- "version_check",
-]
-
[[package]]
name = "nom"
version = "7.1.3"
@@ -9069,25 +7366,6 @@ dependencies = [
"minimal-lexical",
]
-[[package]]
-name = "notify"
-version = "6.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
-dependencies = [
- "bitflags 2.9.4",
- "crossbeam-channel",
- "filetime",
- "fsevent-sys",
- "inotify",
- "kqueue",
- "libc",
- "log",
- "mio 0.8.11",
- "walkdir",
- "windows-sys 0.48.0",
-]
-
[[package]]
name = "ntapi"
version = "0.4.3"
@@ -9112,7 +7390,7 @@ version = "0.101.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71f7c8ed6ba88a567ec6f7c4cad4a7a8465ab93b8cdaf89d3dc72347a83c2d1f"
dependencies = [
- "heck 0.5.0",
+ "heck",
"proc-macro-error",
"proc-macro2",
"quote",
@@ -9180,7 +7458,7 @@ dependencies = [
"dirs 5.0.1",
"dirs-sys 0.4.1",
"fancy-regex 0.14.0",
- "heck 0.5.0",
+ "heck",
"indexmap 2.14.0",
"log",
"lru 0.12.5",
@@ -9283,7 +7561,6 @@ dependencies = [
"num-iter",
"num-traits",
"rand 0.8.5",
- "serde",
"smallvec",
"zeroize",
]
@@ -9300,9 +7577,9 @@ dependencies = [
[[package]]
name = "num-conv"
-version = "0.2.1"
+version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967"
+checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
[[package]]
name = "num-format"
@@ -9402,7 +7679,7 @@ dependencies = [
"base64 0.22.1",
"chrono",
"getrandom 0.2.17",
- "http 1.4.0",
+ "http 1.4.1",
"rand 0.8.5",
"reqwest 0.12.28",
"serde",
@@ -9413,15 +7690,6 @@ dependencies = [
"url",
]
-[[package]]
-name = "objc"
-version = "0.2.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
-dependencies = [
- "malloc_buf",
-]
-
[[package]]
name = "object"
version = "0.37.3"
@@ -9442,11 +7710,11 @@ dependencies = [
"chrono",
"form_urlencoded",
"futures",
- "http 1.4.0",
+ "http 1.4.1",
"http-body-util",
"httparse",
"humantime",
- "hyper 1.9.0",
+ "hyper 1.10.1",
"itertools 0.14.0",
"md-5 0.10.6",
"parking_lot",
@@ -9477,22 +7745,13 @@ dependencies = [
"cc",
]
-[[package]]
-name = "oid-registry"
-version = "0.6.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9bedf36ffb6ba96c2eb7144ef6270557b52e54b20c0a8e1eb2ff99a6c6959bff"
-dependencies = [
- "asn1-rs 0.5.2",
-]
-
[[package]]
name = "oid-registry"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9"
dependencies = [
- "asn1-rs 0.6.2",
+ "asn1-rs",
]
[[package]]
@@ -9525,7 +7784,7 @@ version = "6.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cc3cbf698f9438986c11a880c90a6d04b9de27575afd28bbf45b154b6c709e2"
dependencies = [
- "bitflags 2.9.4",
+ "bitflags 2.11.1",
"libc",
"once_cell",
"onig_sys",
@@ -9557,8 +7816,8 @@ dependencies = [
"chrono",
"dyn-clone",
"ed25519-dalek",
- "hmac 0.12.1",
- "http 1.4.0",
+ "hmac",
+ "http 1.4.1",
"itertools 0.10.5",
"log",
"oauth2",
@@ -9580,15 +7839,14 @@ dependencies = [
[[package]]
name = "openssl"
-version = "0.10.78"
+version = "0.10.80"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f38c4372413cdaaf3cc79dd92d29d7d9f5ab09b51b10dded508fb90bb70b9222"
+checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967"
dependencies = [
- "bitflags 2.9.4",
+ "bitflags 2.11.1",
"cfg-if",
- "foreign-types 0.3.2",
+ "foreign-types",
"libc",
- "once_cell",
"openssl-macros",
"openssl-sys",
]
@@ -9627,9 +7885,9 @@ dependencies = [
[[package]]
name = "openssl-sys"
-version = "0.9.114"
+version = "0.9.116"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "13ce1245cd07fcc4cfdb438f7507b0c7e4f3849a69fd84d52374c66d83741bb6"
+checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4"
dependencies = [
"cc",
"libc",
@@ -9686,7 +7944,7 @@ checksum = "10a8a7f5f6ba7c1b286c2fbca0454eaba116f63bbe69ed250b642d36fbb04d80"
dependencies = [
"async-trait",
"bytes",
- "http 1.4.0",
+ "http 1.4.1",
"opentelemetry 0.27.1",
]
@@ -9698,7 +7956,7 @@ checksum = "50f6639e842a97dbea8886e3439710ae463120091e2e064518ba8e716e6ac36d"
dependencies = [
"async-trait",
"bytes",
- "http 1.4.0",
+ "http 1.4.1",
"opentelemetry 0.30.0",
"reqwest 0.12.28",
]
@@ -9711,7 +7969,7 @@ checksum = "91cf61a1868dacc576bf2b2a1c3e9ab150af7272909e80085c3173384fe11f76"
dependencies = [
"async-trait",
"futures-core",
- "http 1.4.0",
+ "http 1.4.1",
"opentelemetry 0.27.1",
"opentelemetry-http 0.27.0",
"opentelemetry-proto 0.27.0",
@@ -9730,7 +7988,7 @@ version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbee664a43e07615731afc539ca60c6d9f1a9425e25ca09c57bc36c87c55852b"
dependencies = [
- "http 1.4.0",
+ "http 1.4.1",
"opentelemetry 0.30.0",
"opentelemetry-http 0.30.0",
"opentelemetry-proto 0.30.0",
@@ -9800,6 +8058,8 @@ dependencies = [
"rand 0.8.5",
"serde_json",
"thiserror 1.0.69",
+ "tokio",
+ "tokio-stream",
"tracing",
]
@@ -9874,20 +8134,14 @@ dependencies = [
[[package]]
name = "os_pipe"
-version = "1.1.5"
+version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "57119c3b893986491ec9aa85056780d3a0f3cf4da7cc09dd3650dbd6c6738fb9"
+checksum = "5ffd2b0a5634335b135d5728d84c5e0fd726954b87111f7506a61c502280d982"
dependencies = [
"libc",
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
]
-[[package]]
-name = "outref"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f222829ae9293e33a9f5e9f440c6760a3d450a64affe1846486b140db81c1f4"
-
[[package]]
name = "outref"
version = "0.5.2"
@@ -9908,18 +8162,6 @@ version = "4.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d"
-[[package]]
-name = "p224"
-version = "0.13.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "30c06436d66652bc2f01ade021592c80a2aad401570a18aa18b82e440d2b9aa1"
-dependencies = [
- "ecdsa",
- "elliptic-curve",
- "primeorder",
- "sha2 0.10.9",
-]
-
[[package]]
name = "p256"
version = "0.13.2"
@@ -9945,17 +8187,12 @@ dependencies = [
]
[[package]]
-name = "p521"
-version = "0.13.3"
+name = "par-core"
+version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fc9e2161f1f215afdfce23677034ae137bbd45016a880c2eb3ba8eb95f085b2"
+checksum = "e96cbd21255b7fb29a5d51ef38a779b517a91abd59e2756c039583f43ef4c90f"
dependencies = [
- "base16ct",
- "ecdsa",
- "elliptic-curve",
- "primeorder",
- "rand_core 0.6.4",
- "sha2 0.10.9",
+ "once_cell",
]
[[package]]
@@ -10002,7 +8239,7 @@ dependencies = [
"arrow-schema",
"arrow-select",
"base64 0.22.1",
- "brotli 8.0.2",
+ "brotli 8.0.3",
"bytes",
"chrono",
"flate2",
@@ -10019,7 +8256,7 @@ dependencies = [
"snap",
"thrift",
"tokio",
- "twox-hash 2.1.2",
+ "twox-hash",
"zstd",
]
@@ -10042,15 +8279,9 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "pastey"
-version = "0.2.2"
+version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c5a797f0e07bdf071d15742978fc3128ec6c22891c31a3a931513263904c982a"
-
-[[package]]
-name = "path-clean"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ecba01bf2678719532c5e3059e0b5f0811273d94b397088b82e3bd0a78c78fdd"
+checksum = "2ee67f1008b1ba2321834326597b8e186293b049a023cdef258527550b9935b4"
[[package]]
name = "pathdiff"
@@ -10058,16 +8289,6 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
-[[package]]
-name = "pbkdf2"
-version = "0.12.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
-dependencies = [
- "digest 0.10.7",
- "hmac 0.12.1",
-]
-
[[package]]
name = "pem"
version = "1.1.1"
@@ -10195,6 +8416,16 @@ dependencies = [
"phf_shared 0.12.1",
]
+[[package]]
+name = "phf"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf"
+dependencies = [
+ "phf_shared 0.13.1",
+ "serde",
+]
+
[[package]]
name = "phf_codegen"
version = "0.11.3"
@@ -10234,7 +8465,7 @@ version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
dependencies = [
- "siphasher 1.0.2",
+ "siphasher 1.0.3",
]
[[package]]
@@ -10243,7 +8474,16 @@ version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981"
dependencies = [
- "siphasher 1.0.2",
+ "siphasher 1.0.3",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266"
+dependencies = [
+ "siphasher 1.0.3",
]
[[package]]
@@ -10260,18 +8500,18 @@ dependencies = [
[[package]]
name = "pin-project"
-version = "1.1.11"
+version = "1.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517"
+checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
-version = "1.1.11"
+version = "1.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6"
+checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b"
dependencies = [
"proc-macro2",
"quote",
@@ -10301,21 +8541,6 @@ dependencies = [
"spki",
]
-[[package]]
-name = "pkcs5"
-version = "0.7.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e847e2c91a18bfa887dd028ec33f2fe6f25db77db3619024764914affe8b69a6"
-dependencies = [
- "aes 0.8.3",
- "cbc",
- "der",
- "pbkdf2",
- "scrypt",
- "sha2 0.10.9",
- "spki",
-]
-
[[package]]
name = "pkcs8"
version = "0.10.2"
@@ -10323,8 +8548,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
dependencies = [
"der",
- "pkcs5",
- "rand_core 0.6.4",
"spki",
]
@@ -10340,19 +8563,6 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
-[[package]]
-name = "png"
-version = "0.17.16"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"
-dependencies = [
- "bitflags 1.3.2",
- "crc32fast",
- "fdeflate",
- "flate2",
- "miniz_oxide 0.8.9",
-]
-
[[package]]
name = "polyval"
version = "0.6.2"
@@ -10373,85 +8583,56 @@ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
[[package]]
name = "postgres-native-tls"
-version = "0.5.0"
-source = "git+https://github.com/imor/rust-postgres?rev=20265ef38e32a06f76b6f9b678e2077fc2211f6b#20265ef38e32a06f76b6f9b678e2077fc2211f6b"
+version = "0.5.2"
+source = "git+https://github.com/MaterializeInc/rust-postgres?rev=78c1222577bb091d69bc22b1bc7ad01c14675abe#78c1222577bb091d69bc22b1bc7ad01c14675abe"
dependencies = [
"native-tls",
"tokio",
"tokio-native-tls",
- "tokio-postgres 0.7.11",
+ "tokio-postgres",
]
[[package]]
name = "postgres-native-tls"
-version = "0.5.1"
+version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1f39498473c92f7b6820ae970382c1d83178a3454c618161cb772e8598d9f6f"
+checksum = "fef4de47bb81477e0c3deaf153a1b10ae176484713ff1640969f4cb96b653ebc"
dependencies = [
"native-tls",
"tokio",
"tokio-native-tls",
- "tokio-postgres 0.7.13",
+ "tokio-postgres",
]
[[package]]
name = "postgres-protocol"
-version = "0.6.7"
-source = "git+https://github.com/imor/rust-postgres?rev=20265ef38e32a06f76b6f9b678e2077fc2211f6b#20265ef38e32a06f76b6f9b678e2077fc2211f6b"
+version = "0.6.9"
+source = "git+https://github.com/MaterializeInc/rust-postgres?rev=78c1222577bb091d69bc22b1bc7ad01c14675abe#78c1222577bb091d69bc22b1bc7ad01c14675abe"
dependencies = [
"base64 0.22.1",
"byteorder",
"bytes",
- "fallible-iterator 0.2.0",
- "hmac 0.12.1",
+ "fallible-iterator",
+ "hmac",
"md-5 0.10.6",
"memchr",
- "rand 0.8.5",
+ "rand 0.9.0",
"sha2 0.10.9",
"stringprep",
]
-[[package]]
-name = "postgres-protocol"
-version = "0.6.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56201207dac53e2f38e848e31b4b91616a6bb6e0c7205b77718994a7f49e70fc"
-dependencies = [
- "base64 0.22.1",
- "byteorder",
- "bytes",
- "fallible-iterator 0.2.0",
- "hmac 0.13.0",
- "md-5 0.11.0",
- "memchr",
- "rand 0.10.1",
- "sha2 0.11.0",
- "stringprep",
-]
-
[[package]]
name = "postgres-types"
-version = "0.2.7"
-source = "git+https://github.com/imor/rust-postgres?rev=20265ef38e32a06f76b6f9b678e2077fc2211f6b#20265ef38e32a06f76b6f9b678e2077fc2211f6b"
-dependencies = [
- "bytes",
- "fallible-iterator 0.2.0",
- "postgres-protocol 0.6.7",
-]
-
-[[package]]
-name = "postgres-types"
-version = "0.2.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "613283563cd90e1dfc3518d548caee47e0e725455ed619881f5cf21f36de4b48"
+version = "0.2.11"
+source = "git+https://github.com/MaterializeInc/rust-postgres?rev=78c1222577bb091d69bc22b1bc7ad01c14675abe#78c1222577bb091d69bc22b1bc7ad01c14675abe"
dependencies = [
"array-init",
"bit-vec 0.6.3",
"bytes",
"chrono",
- "fallible-iterator 0.2.0",
- "postgres-protocol 0.6.11",
- "serde",
+ "fallible-iterator",
+ "postgres-protocol",
+ "serde_core",
"serde_json",
"uuid",
]
@@ -10507,11 +8688,11 @@ dependencies = [
[[package]]
name = "proc-macro-crate"
-version = "3.4.0"
+version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983"
+checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f"
dependencies = [
- "toml_edit 0.23.4",
+ "toml_edit 0.25.12+spec-1.1.0",
]
[[package]]
@@ -10523,7 +8704,6 @@ dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
- "syn 1.0.109",
"version_check",
]
@@ -10612,7 +8792,7 @@ version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc5b72d8145275d844d4b5f6d4e1eef00c8cd889edb6035c21675d1bb1f45c9f"
dependencies = [
- "bitflags 2.9.4",
+ "bitflags 2.11.1",
"chrono",
"flate2",
"hex",
@@ -10626,17 +8806,11 @@ version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "239df02d8349b06fc07398a3a1697b06418223b1c7725085e801e7c0fc6a12ec"
dependencies = [
- "bitflags 2.9.4",
+ "bitflags 2.11.1",
"chrono",
"hex",
]
-[[package]]
-name = "profiling"
-version = "1.0.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773"
-
[[package]]
name = "prometheus"
version = "0.14.0"
@@ -10661,26 +8835,6 @@ dependencies = [
"prost-derive",
]
-[[package]]
-name = "prost-build"
-version = "0.13.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf"
-dependencies = [
- "heck 0.5.0",
- "itertools 0.14.0",
- "log",
- "multimap",
- "once_cell",
- "petgraph",
- "prettyplease",
- "prost",
- "prost-types",
- "regex",
- "syn 2.0.117",
- "tempfile",
-]
-
[[package]]
name = "prost-derive"
version = "0.13.5"
@@ -10739,7 +8893,7 @@ version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b"
dependencies = [
- "bitflags 2.9.4",
+ "bitflags 2.11.1",
"getopts",
"memchr",
"unicase",
@@ -10796,13 +8950,13 @@ dependencies = [
[[package]]
name = "quick_cache"
-version = "0.6.21"
+version = "0.6.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a70b1b8b47e31d0498ecbc3c5470bb931399a8bfed1fd79d1717a61ce7f96e3"
+checksum = "d1c821816e9b928e20e92ed59bb3ac4aab321d16ca2316871c9fe7ca739cd477"
dependencies = [
"ahash 0.8.12",
"equivalent",
- "hashbrown 0.16.0",
+ "hashbrown 0.16.1",
"parking_lot",
]
@@ -10813,13 +8967,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20"
dependencies = [
"bytes",
- "cfg_aliases 0.2.1",
+ "cfg_aliases",
"pin-project-lite",
"quinn-proto",
"quinn-udp",
"rustc-hash 2.1.2",
"rustls 0.23.35",
- "socket2 0.6.3",
+ "socket2 0.6.4",
"thiserror 2.0.18",
"tokio",
"tracing",
@@ -10854,10 +9008,10 @@ version = "0.5.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd"
dependencies = [
- "cfg_aliases 0.2.1",
+ "cfg_aliases",
"libc",
"once_cell",
- "socket2 0.6.3",
+ "socket2 0.6.4",
"tracing",
"windows-sys 0.60.2",
]
@@ -10889,16 +9043,6 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
-[[package]]
-name = "radix_trie"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd"
-dependencies = [
- "endian-type",
- "nibble_vec",
-]
-
[[package]]
name = "rand"
version = "0.7.3"
@@ -11027,27 +9171,15 @@ dependencies = [
"rand_core 0.5.1",
]
-[[package]]
-name = "range-alloc"
-version = "0.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ca45419789ae5a7899559e9512e58ca889e41f04f1f2445e9f4b290ceccd1d08"
-
[[package]]
name = "raw-cpuid"
version = "11.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186"
dependencies = [
- "bitflags 2.9.4",
+ "bitflags 2.11.1",
]
-[[package]]
-name = "raw-window-handle"
-version = "0.6.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
-
[[package]]
name = "rayon"
version = "1.12.0"
@@ -11089,7 +9221,7 @@ dependencies = [
"ring 0.17.14",
"rustls-pki-types",
"time",
- "x509-parser 0.16.0",
+ "x509-parser",
"yasna",
]
@@ -11118,6 +9250,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e234cf318915c1059d4921ef7f75616b5219b10b46e9f3a511a15eb4b56a3f77"
dependencies = [
"cmake",
+ "curl-sys",
"libc",
"libz-sys",
"num_enum",
@@ -11158,16 +9291,16 @@ version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
- "bitflags 2.9.4",
+ "bitflags 2.11.1",
]
[[package]]
name = "redox_syscall"
-version = "0.7.4"
+version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a"
+checksum = "7c7591fa2c6b601dfcfe5f043f65a1c39fcdf50efefcd7f1572e538c1f4b398d"
dependencies = [
- "bitflags 2.9.4",
+ "bitflags 2.11.1",
]
[[package]]
@@ -11283,11 +9416,11 @@ dependencies = [
"futures-channel",
"futures-core",
"futures-util",
- "h2 0.4.13",
- "http 1.4.0",
+ "h2 0.4.14",
+ "http 1.4.1",
"http-body 1.0.1",
"http-body-util",
- "hyper 1.9.0",
+ "hyper 1.10.1",
"hyper-rustls 0.27.9",
"hyper-tls",
"hyper-util",
@@ -11331,11 +9464,11 @@ dependencies = [
"encoding_rs",
"futures-core",
"futures-util",
- "h2 0.4.13",
- "http 1.4.0",
+ "h2 0.4.14",
+ "http 1.4.1",
"http-body 1.0.1",
"http-body-util",
- "hyper 1.9.0",
+ "hyper 1.10.1",
"hyper-rustls 0.27.9",
"hyper-util",
"js-sys",
@@ -11367,13 +9500,13 @@ dependencies = [
[[package]]
name = "reqwest-middleware"
-version = "0.5.1"
+version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "199dda04a536b532d0cc04d7979e39b1c763ea749bf91507017069c00b96056f"
+checksum = "07bc3f1384cffa4f274dad2d4ddd73aed32fed8f786d96c6be8aa4e5fd3c3b58"
dependencies = [
"anyhow",
"async-trait",
- "http 1.4.0",
+ "http 1.4.1",
"reqwest 0.13.1",
"serde",
"thiserror 2.0.18",
@@ -11390,8 +9523,8 @@ dependencies = [
"async-trait",
"futures",
"getrandom 0.2.17",
- "http 1.4.0",
- "hyper 1.9.0",
+ "http 1.4.1",
+ "hyper 1.10.1",
"reqwest 0.13.1",
"reqwest-middleware",
"retry-policies",
@@ -11409,11 +9542,11 @@ checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7"
[[package]]
name = "retry-policies"
-version = "0.5.0"
+version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "503c78f59814e2664c9980b739b19e40f549233ecf39928040ee54fdd431f614"
+checksum = "dc05fbf560421a0357a750cbe78c7ca19d4923918490daabba313d5dbc871e47"
dependencies = [
- "rand 0.8.5",
+ "rand 0.10.1",
]
[[package]]
@@ -11422,7 +9555,7 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2"
dependencies = [
- "hmac 0.12.1",
+ "hmac",
"subtle",
]
@@ -11455,15 +9588,6 @@ dependencies = [
"windows-sys 0.52.0",
]
-[[package]]
-name = "ripemd"
-version = "0.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f"
-dependencies = [
- "digest 0.10.7",
-]
-
[[package]]
name = "rkyv"
version = "0.7.46"
@@ -11500,12 +9624,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bef41ebc9ebed2c1b1d90203e9d1756091e8a00bbc3107676151f39868ca0ee"
dependencies = [
"async-trait",
- "axum 0.8.4",
+ "axum 0.8.9",
"base64 0.22.1",
"bytes",
"chrono",
"futures",
- "http 1.4.0",
+ "http 1.4.1",
"http-body 1.0.1",
"http-body-util",
"oauth2",
@@ -11541,18 +9665,6 @@ dependencies = [
"syn 2.0.117",
]
-[[package]]
-name = "ron"
-version = "0.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94"
-dependencies = [
- "base64 0.21.7",
- "bitflags 2.9.4",
- "serde",
- "serde_derive",
-]
-
[[package]]
name = "rquickjs"
version = "0.11.0"
@@ -11570,7 +9682,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8bf7840285c321c3ab20e752a9afb95548c75cd7f4632a0627cea3507e310c1"
dependencies = [
"async-lock",
- "hashbrown 0.16.0",
+ "hashbrown 0.16.1",
"relative-path",
"rquickjs-sys",
]
@@ -11607,7 +9719,7 @@ version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d"
dependencies = [
- "const-oid 0.9.6",
+ "const-oid",
"digest 0.10.7",
"num-bigint-dig",
"num-integer",
@@ -11641,20 +9753,6 @@ dependencies = [
"tokio-rustls 0.25.0",
]
-[[package]]
-name = "rusqlite"
-version = "0.32.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e"
-dependencies = [
- "bitflags 2.9.4",
- "fallible-iterator 0.3.0",
- "fallible-streaming-iterator",
- "hashlink 0.9.1",
- "libsqlite3-sys",
- "smallvec",
-]
-
[[package]]
name = "rust-embed"
version = "6.8.1"
@@ -11702,15 +9800,15 @@ dependencies = [
[[package]]
name = "rust_decimal"
-version = "1.41.0"
+version = "1.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2ce901f9a19d251159075a4c37af514c3b8ef99c22e02dd8c19161cf397ee94a"
+checksum = "0c5108e3d4d903e21aac27f12ba5377b6b34f9f44b325e4894c7924169d06995"
dependencies = [
"arrayvec",
"borsh",
"bytes",
"num-traits",
- "postgres-types 0.2.9",
+ "postgres-types",
"rand 0.8.5",
"rkyv",
"serde",
@@ -11760,7 +9858,7 @@ version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632"
dependencies = [
- "nom 7.1.3",
+ "nom",
]
[[package]]
@@ -11769,7 +9867,7 @@ version = "0.38.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
dependencies = [
- "bitflags 2.9.4",
+ "bitflags 2.11.1",
"errno",
"libc",
"linux-raw-sys 0.4.15",
@@ -11782,7 +9880,7 @@ version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
dependencies = [
- "bitflags 2.9.4",
+ "bitflags 2.11.1",
"errno",
"libc",
"linux-raw-sys 0.12.1",
@@ -11865,7 +9963,7 @@ dependencies = [
"openssl-probe 0.2.1",
"rustls-pki-types",
"schannel",
- "security-framework 3.6.0",
+ "security-framework 3.7.0",
]
[[package]]
@@ -11911,9 +10009,9 @@ dependencies = [
"rustls-native-certs 0.8.3",
"rustls-platform-verifier-android",
"rustls-webpki 0.103.13",
- "security-framework 3.6.0",
+ "security-framework 3.7.0",
"security-framework-sys",
- "webpki-root-certs 1.0.7",
+ "webpki-root-certs",
"windows-sys 0.61.2",
]
@@ -11925,9 +10023,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
[[package]]
name = "rustls-tokio-stream"
-version = "0.3.0"
+version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22557157d7395bc30727745b365d923f1ecc230c4c80b176545f3f4f08c46e33"
+checksum = "faa7dc7c991d9164e55bbf1558029eb5b84d32cc4d61a7df5b8641b2deedc4b3"
dependencies = [
"futures",
"rustls 0.23.35",
@@ -12031,28 +10129,6 @@ version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
-[[package]]
-name = "rustyline"
-version = "13.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "02a2d683a4ac90aeef5b1013933f6d977bd37d51ff3f4dad829d4931a7e6be86"
-dependencies = [
- "bitflags 2.9.4",
- "cfg-if",
- "clipboard-win",
- "fd-lock",
- "home",
- "libc",
- "log",
- "memchr",
- "nix 0.27.1",
- "radix_trie",
- "unicode-segmentation",
- "unicode-width 0.1.14",
- "utf8parse",
- "winapi",
-]
-
[[package]]
name = "ryu"
version = "1.0.23"
@@ -12071,30 +10147,11 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "675656c1eabb620b921efea4f9199f97fc86e36dd6ffd1fbbe48d0f59a4987f5"
dependencies = [
- "hashbrown 0.16.0",
+ "hashbrown 0.16.1",
"serde",
"serde_json",
]
-[[package]]
-name = "saffron"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "03fb9a628596fc7590eb7edbf7b0613287be78df107f5f97b118aad59fb2eea9"
-dependencies = [
- "chrono",
- "nom 5.1.3",
-]
-
-[[package]]
-name = "salsa20"
-version = "0.10.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213"
-dependencies = [
- "cipher 0.4.4",
-]
-
[[package]]
name = "samael"
version = "0.0.20"
@@ -12148,15 +10205,6 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ece8e78b2f38ec51c51f5d475df0a7187ba5111b2a28bdc761ee05b075d40a71"
-[[package]]
-name = "scc"
-version = "2.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "46e6f046b7fef48e2660c57ed794263155d713de679057f2d0c169bfc6e756cc"
-dependencies = [
- "sdd",
-]
-
[[package]]
name = "schannel"
version = "0.1.29"
@@ -12240,18 +10288,6 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
-[[package]]
-name = "scrypt"
-version = "0.11.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f"
-dependencies = [
- "password-hash",
- "pbkdf2",
- "salsa20",
- "sha2 0.10.9",
-]
-
[[package]]
name = "sct"
version = "0.7.1"
@@ -12262,12 +10298,6 @@ dependencies = [
"untrusted 0.9.0",
]
-[[package]]
-name = "sdd"
-version = "3.0.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca"
-
[[package]]
name = "seahash"
version = "4.1.0"
@@ -12284,7 +10314,6 @@ dependencies = [
"der",
"generic-array",
"pkcs8",
- "serdect",
"subtle",
"zeroize",
]
@@ -12304,7 +10333,7 @@ version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
dependencies = [
- "bitflags 2.9.4",
+ "bitflags 2.11.1",
"core-foundation 0.9.4",
"core-foundation-sys",
"libc",
@@ -12313,11 +10342,11 @@ dependencies = [
[[package]]
name = "security-framework"
-version = "3.6.0"
+version = "3.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d17b898a6d6948c3a8ee4372c17cb384f90d2e6e912ef00895b14fd7ab54ec38"
+checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
dependencies = [
- "bitflags 2.9.4",
+ "bitflags 2.11.1",
"core-foundation 0.10.1",
"core-foundation-sys",
"libc",
@@ -12363,9 +10392,9 @@ checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc"
[[package]]
name = "serde"
-version = "1.0.220"
+version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ceecad4c782e936ac90ecfd6b56532322e3262b14320abf30ce89a92ffdbfe22"
+checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
@@ -12404,30 +10433,20 @@ dependencies = [
"wasm-bindgen",
]
-[[package]]
-name = "serde_bytes"
-version = "0.11.19"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8"
-dependencies = [
- "serde",
- "serde_core",
-]
-
[[package]]
name = "serde_core"
-version = "1.0.220"
+version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ddba47394f3b862d6ff6efdbd26ca4673e3566a307880a0ffb98f274bbe0ec32"
+checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.220"
+version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "60e1f3b1761e96def5ec6d04a6e7421c0404fa3cf5c0155f1e2848fae3d8cc08"
+checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
@@ -12447,9 +10466,9 @@ dependencies = [
[[package]]
name = "serde_json"
-version = "1.0.149"
+version = "1.0.150"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
+checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
dependencies = [
"indexmap 2.14.0",
"itoa",
@@ -12522,11 +10541,11 @@ dependencies = [
[[package]]
name = "serde_v8"
-version = "0.245.0"
+version = "0.261.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "945f93c91e0c7e4799b5fefff076756141aae92e262c4dc4833310dd3d2d845e"
+checksum = "3495190857461e87a2716141043218aad5281f219f54a03b7ebbe605b3b931df"
dependencies = [
- "deno_error",
+ "deno_error 0.6.1",
"num-bigint",
"serde",
"smallvec",
@@ -12536,19 +10555,19 @@ dependencies = [
[[package]]
name = "serde_with"
-version = "3.14.1"
+version = "3.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c522100790450cf78eeac1507263d0a350d4d5b30df0c8e1fe051a10c22b376e"
+checksum = "e72c1c2cb7b223fafb600a619537a871c2818583d619401b785e7c0b746ccde2"
dependencies = [
"base64 0.22.1",
+ "bs58",
"chrono",
"hex",
"indexmap 1.9.3",
"indexmap 2.14.0",
"schemars 0.9.0",
"schemars 1.2.1",
- "serde",
- "serde_derive",
+ "serde_core",
"serde_json",
"serde_with_macros",
"time",
@@ -12556,11 +10575,11 @@ dependencies = [
[[package]]
name = "serde_with_macros"
-version = "3.14.1"
+version = "3.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "327ada00f7d64abaac1e55a6911e90cf665aa051b9a561c7006c157f4633135e"
+checksum = "b90c488738ecb4fb0262f41f43bc40efc5868d9fb744319ddf5f5317f417bfac"
dependencies = [
- "darling 0.21.3",
+ "darling 0.23.0",
"proc-macro2",
"quote",
"syn 2.0.117",
@@ -12594,36 +10613,25 @@ dependencies = [
"version_check",
]
-[[package]]
-name = "serdect"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177"
-dependencies = [
- "base16ct",
- "serde",
-]
-
[[package]]
name = "serial_test"
-version = "3.4.0"
+version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "911bd979bf1070a3f3aa7b691a3b3e9968f339ceeec89e08c280a8a22207a32f"
+checksum = "699f4197115b8a7e7ff19c9a315a4bd6fffec26cc4626ef45ecaea389e081c6d"
dependencies = [
"futures-executor",
"futures-util",
"log",
"once_cell",
"parking_lot",
- "scc",
"serial_test_derive",
]
[[package]]
name = "serial_test_derive"
-version = "3.4.0"
+version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0a7d91949b85b0d2fb687445e448b40d322b6b3e4af6b44a29b21d9a5f33e6d9"
+checksum = "94e153fc76e1c6a068703d6d29c508a0b15c061c4b7e43da59cc097bc342673c"
dependencies = [
"proc-macro2",
"quote",
@@ -12665,27 +10673,6 @@ dependencies = [
"digest 0.10.7",
]
-[[package]]
-name = "sha2"
-version = "0.11.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
-dependencies = [
- "cfg-if",
- "cpufeatures 0.3.0",
- "digest 0.11.2",
-]
-
-[[package]]
-name = "sha3"
-version = "0.10.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874"
-dependencies = [
- "digest 0.10.7",
- "keccak",
-]
-
[[package]]
name = "sharded-slab"
version = "0.1.7"
@@ -12721,14 +10708,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
-name = "signal-hook"
-version = "0.3.18"
+name = "shlex"
+version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2"
-dependencies = [
- "libc",
- "signal-hook-registry",
-]
+checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
[[package]]
name = "signal-hook-registry"
@@ -12762,36 +10745,12 @@ dependencies = [
"rand_core 0.6.4",
]
-[[package]]
-name = "simd-abstraction"
-version = "0.7.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9cadb29c57caadc51ff8346233b5cec1d240b68ce55cf1afc764818791876987"
-dependencies = [
- "outref 0.1.0",
-]
-
[[package]]
name = "simd-adler32"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
-[[package]]
-name = "simd-json"
-version = "0.14.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aa2bcf6c6e164e81bc7a5d49fc6988b3d515d9e8c07457d7b74ffb9324b9cd40"
-dependencies = [
- "getrandom 0.2.17",
- "halfbrown",
- "ref-cast",
- "serde",
- "serde_json",
- "simdutf8",
- "value-trait",
-]
-
[[package]]
name = "simdutf8"
version = "0.1.5"
@@ -12818,9 +10777,9 @@ checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
[[package]]
name = "siphasher"
-version = "1.0.2"
+version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
+checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649"
[[package]]
name = "size"
@@ -12843,24 +10802,6 @@ version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
-[[package]]
-name = "slotmap"
-version = "1.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038"
-dependencies = [
- "version_check",
-]
-
-[[package]]
-name = "sm3"
-version = "0.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ebb9a3b702d0a7e33bc4d85a14456633d2b165c2ad839c5fd9a8417c1ab15860"
-dependencies = [
- "digest 0.10.7",
-]
-
[[package]]
name = "smallvec"
version = "1.15.1"
@@ -12905,9 +10846,9 @@ dependencies = [
[[package]]
name = "socket2"
-version = "0.6.3"
+version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
+checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51"
dependencies = [
"libc",
"windows-sys 0.61.2",
@@ -12924,32 +10865,13 @@ dependencies = [
"winapi",
]
-[[package]]
-name = "sourcemap"
-version = "8.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "208d40b9e8cad9f93613778ea295ed8f3c2b1824217c6cfc7219d3f6f45b96d4"
-dependencies = [
- "base64-simd 0.7.0",
- "bitvec",
- "data-encoding",
- "debugid",
- "if_chain",
- "rustc-hash 1.1.0",
- "rustc_version 0.2.3",
- "serde",
- "serde_json",
- "unicode-id-start",
- "url",
-]
-
[[package]]
name = "sourcemap"
version = "9.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "314d62a489431668f719ada776ca1d49b924db951b7450f8974c9ae51ab05ad7"
dependencies = [
- "base64-simd 0.8.0",
+ "base64-simd",
"bitvec",
"data-encoding",
"debugid",
@@ -12976,15 +10898,6 @@ dependencies = [
"lock_api",
]
-[[package]]
-name = "spirv"
-version = "0.3.0+sdk-1.3.268.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844"
-dependencies = [
- "bitflags 2.9.4",
-]
-
[[package]]
name = "spki"
version = "0.7.3"
@@ -13002,17 +10915,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5851699c4033c63636f7ea4cf7b7c1f1bf06d0cc03cfb42e711de5a5c46cf326"
dependencies = [
"base64 0.13.1",
- "nom 7.1.3",
+ "nom",
"serde",
"unicode-segmentation",
]
-[[package]]
-name = "sptr"
-version = "0.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3b9b39299b249ad65f3b7e96443bad61c02ca5cd3589f46cb6d610a0fd6c0d6a"
-
[[package]]
name = "sql-builder"
version = "3.1.1"
@@ -13088,7 +10995,7 @@ dependencies = [
"futures-io",
"futures-util",
"hashbrown 0.15.5",
- "hashlink 0.10.0",
+ "hashlink",
"indexmap 2.14.0",
"log",
"memchr",
@@ -13129,7 +11036,7 @@ checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b"
dependencies = [
"dotenvy",
"either",
- "heck 0.5.0",
+ "heck",
"hex",
"once_cell",
"proc-macro2",
@@ -13155,7 +11062,7 @@ dependencies = [
"atoi",
"base64 0.22.1",
"bigdecimal",
- "bitflags 2.9.4",
+ "bitflags 2.11.1",
"byteorder",
"bytes",
"chrono",
@@ -13170,7 +11077,7 @@ dependencies = [
"generic-array",
"hex",
"hkdf",
- "hmac 0.12.1",
+ "hmac",
"itoa",
"log",
"md-5 0.10.6",
@@ -13200,7 +11107,7 @@ dependencies = [
"atoi",
"base64 0.22.1",
"bigdecimal",
- "bitflags 2.9.4",
+ "bitflags 2.11.1",
"byteorder",
"chrono",
"crc",
@@ -13211,7 +11118,7 @@ dependencies = [
"futures-util",
"hex",
"hkdf",
- "hmac 0.12.1",
+ "hmac",
"home",
"itoa",
"log",
@@ -13298,11 +11205,10 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "string_enum"
-version = "0.4.4"
+version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05e383308aebc257e7d7920224fa055c632478d92744eca77f99be8fa1545b90"
+checksum = "ae36a4951ca7bd1cfd991c241584a9824a70f6aff1e7d4f693fb3f2465e4030e"
dependencies = [
- "proc-macro2",
"quote",
"swc_macros_common",
"syn 2.0.117",
@@ -13310,9 +11216,9 @@ dependencies = [
[[package]]
name = "stringcase"
-version = "0.3.0"
+version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "04028eeb851ed08af6aba5caa29f2d59a13ed168cee4d6bd753aeefcf1d636b0"
+checksum = "72abeda133c49d7bddece6c154728f83eec8172380c80ab7096da9487e20d27c"
[[package]]
name = "stringprep"
@@ -13346,35 +11252,13 @@ version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
-[[package]]
-name = "strum"
-version = "0.25.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
-dependencies = [
- "strum_macros 0.25.3",
-]
-
[[package]]
name = "strum"
version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf"
dependencies = [
- "strum_macros 0.27.2",
-]
-
-[[package]]
-name = "strum_macros"
-version = "0.25.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0"
-dependencies = [
- "heck 0.4.1",
- "proc-macro2",
- "quote",
- "rustversion",
- "syn 2.0.117",
+ "strum_macros",
]
[[package]]
@@ -13383,7 +11267,7 @@ version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7"
dependencies = [
- "heck 0.5.0",
+ "heck",
"proc-macro2",
"quote",
"syn 2.0.117",
@@ -13418,64 +11302,48 @@ checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2"
[[package]]
name = "swc_allocator"
-version = "0.1.10"
+version = "4.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "76aa0eb65c0f39f9b6d82a7e5192c30f7ac9a78f084a21f270de1d8c600ca388"
+checksum = "9d7eefd2c8b228a8c73056482b2ae4b3a1071fbe07638e3b55ceca8570cc48bb"
dependencies = [
+ "allocator-api2",
"bumpalo",
"hashbrown 0.14.5",
- "ptr_meta",
- "rustc-hash 1.1.0",
- "triomphe",
+ "rustc-hash 2.1.2",
]
[[package]]
name = "swc_atoms"
-version = "0.6.7"
+version = "7.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bb6567e4e67485b3e7662b486f1565bdae54bd5b9d6b16b2ba1a9babb1e42125"
+checksum = "3500dcf04c84606b38464561edc5e46f5132201cb3e23cf9613ed4033d6b1bb2"
dependencies = [
"hstr",
"once_cell",
- "rustc-hash 1.1.0",
- "serde",
-]
-
-[[package]]
-name = "swc_cached"
-version = "0.3.20"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "83406221c501860fce9c27444f44125eafe9e598b8b81be7563d7036784cd05c"
-dependencies = [
- "ahash 0.8.12",
- "anyhow",
- "dashmap 5.5.3",
- "once_cell",
- "regex",
"serde",
]
[[package]]
name = "swc_common"
-version = "0.37.5"
+version = "14.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "12d0a8eaaf1606c9207077d75828008cb2dfb51b095a766bd2b72ef893576e31"
+checksum = "c2bb772b3a26b8b71d4e8c112ced5b5867be2266364b58517407a270328a2696"
dependencies = [
+ "anyhow",
"ast_node",
"better_scoped_tls",
- "cfg-if",
+ "bytes-str",
"either",
"from_variant",
"new_debug_unreachable",
"num-bigint",
"once_cell",
- "rustc-hash 1.1.0",
+ "rustc-hash 2.1.2",
"serde",
"siphasher 0.3.11",
- "sourcemap 9.3.2",
- "swc_allocator",
"swc_atoms",
"swc_eq_ignore_macros",
+ "swc_sourcemap",
"swc_visit",
"tracing",
"unicode-width 0.1.14",
@@ -13484,23 +11352,23 @@ dependencies = [
[[package]]
name = "swc_config"
-version = "0.1.15"
+version = "3.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4740e53eaf68b101203c1df0937d5161a29f3c13bceed0836ddfe245b72dd000"
+checksum = "72e90b52ee734ded867104612218101722ad87ff4cf74fe30383bd244a533f97"
dependencies = [
"anyhow",
+ "bytes-str",
"indexmap 2.14.0",
"serde",
"serde_json",
- "swc_cached",
"swc_config_macro",
]
[[package]]
name = "swc_config_macro"
-version = "0.1.4"
+version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7c5f56139042c1a95b54f5ca48baa0e0172d369bcc9d3d473dad1de36bae8399"
+checksum = "7b416e8ce6de17dc5ea496e10c7012b35bbc0e3fef38d2e065eed936490db0b3"
dependencies = [
"proc-macro2",
"quote",
@@ -13510,78 +11378,72 @@ dependencies = [
[[package]]
name = "swc_ecma_ast"
-version = "0.118.2"
+version = "15.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a6f866d12e4d519052b92a0a86d1ac7ff17570da1272ca0c89b3d6f802cd79df"
+checksum = "65c25af97d53cf8aab66a6c68f3418663313fc969ad267fc2a4d19402c329be1"
dependencies = [
- "bitflags 2.9.4",
+ "bitflags 2.11.1",
"is-macro",
"num-bigint",
+ "once_cell",
"phf 0.11.3",
- "scoped-tls",
+ "rustc-hash 2.1.2",
"serde",
"string_enum",
"swc_atoms",
"swc_common",
+ "swc_visit",
"unicode-id-start",
]
[[package]]
name = "swc_ecma_codegen"
-version = "0.155.1"
+version = "17.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc7641608ef117cfbef9581a99d02059b522fcca75e5244fa0cbbd8606689c6f"
+checksum = "bcf55c2d7555c93f4945e29f93b7529562be97ba16e60dd94c25724d746174ac"
dependencies = [
+ "ascii",
+ "compact_str",
"memchr",
"num-bigint",
"once_cell",
+ "regex",
+ "rustc-hash 2.1.2",
+ "ryu-js",
"serde",
- "sourcemap 9.3.2",
"swc_allocator",
"swc_atoms",
"swc_common",
"swc_ecma_ast",
"swc_ecma_codegen_macros",
+ "swc_sourcemap",
"tracing",
]
[[package]]
name = "swc_ecma_codegen_macros"
-version = "0.7.7"
+version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "859fabde36db38634f3fad548dd5e3410c1aebba1b67a3c63e67018fa57a0bca"
+checksum = "e276dc62c0a2625a560397827989c82a93fd545fcf6f7faec0935a82cc4ddbb8"
dependencies = [
"proc-macro2",
- "quote",
"swc_macros_common",
"syn 2.0.117",
]
[[package]]
-name = "swc_ecma_loader"
-version = "0.49.1"
+name = "swc_ecma_lexer"
+version = "23.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "55fa3d55045b97894bfb04d38aff6d6302ac8a6a38e3bb3dfb0d20475c4974a9"
-dependencies = [
- "anyhow",
- "pathdiff",
- "serde",
- "swc_atoms",
- "swc_common",
- "tracing",
-]
-
-[[package]]
-name = "swc_ecma_parser"
-version = "0.149.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "683dada14722714588b56481399c699378b35b2ba4deb5c4db2fb627a97fb54b"
+checksum = "017d06ea85008234aa9fb34d805c7dc563f2ea6e03869ed5ac5a2dc27d561e4d"
dependencies = [
+ "arrayvec",
+ "bitflags 2.11.1",
"either",
- "new_debug_unreachable",
"num-bigint",
- "num-traits",
"phf 0.11.3",
+ "rustc-hash 2.1.2",
+ "seq-macro",
"serde",
"smallvec",
"smartstring",
@@ -13590,23 +11452,52 @@ dependencies = [
"swc_common",
"swc_ecma_ast",
"tracing",
- "typed-arena",
+]
+
+[[package]]
+name = "swc_ecma_loader"
+version = "14.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c675d14700c92f12585049b22b02356f1e142f4b0c32a4d0eb4b7a968a4c0c1e"
+dependencies = [
+ "anyhow",
+ "pathdiff",
+ "rustc-hash 2.1.2",
+ "serde",
+ "swc_atoms",
+ "swc_common",
+ "tracing",
+]
+
+[[package]]
+name = "swc_ecma_parser"
+version = "24.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e9011783c975ba592ffc09cd208ced92b1dfabb2e5e0ef453559e2e25286127"
+dependencies = [
+ "either",
+ "num-bigint",
+ "serde",
+ "swc_atoms",
+ "swc_common",
+ "swc_ecma_ast",
+ "swc_ecma_lexer",
+ "tracing",
]
[[package]]
name = "swc_ecma_transforms_base"
-version = "0.145.0"
+version = "27.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "65f21494e75d0bd8ef42010b47cabab9caaed8f2207570e809f6f4eb51a710d1"
+checksum = "6c6f1b8f4232e7a7f614ff7c0f6ccb89c2d028cdf7629f79ad710cff5b28b62c"
dependencies = [
"better_scoped_tls",
- "bitflags 2.9.4",
"indexmap 2.14.0",
"once_cell",
+ "par-core",
"phf 0.11.3",
- "rustc-hash 1.1.0",
+ "rustc-hash 2.1.2",
"serde",
- "smallvec",
"swc_atoms",
"swc_common",
"swc_ecma_ast",
@@ -13618,11 +11509,10 @@ dependencies = [
[[package]]
name = "swc_ecma_transforms_classes"
-version = "0.134.0"
+version = "27.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c3d884594385bea9405a2e1721151470d9a14d3ceec5dd773c0ca6894791601"
+checksum = "108d4d52db6151f768a516fe86e6f21fc783b03fa2d20292999f29275fd0c71d"
dependencies = [
- "swc_atoms",
"swc_common",
"swc_ecma_ast",
"swc_ecma_transforms_base",
@@ -13632,9 +11522,9 @@ dependencies = [
[[package]]
name = "swc_ecma_transforms_macros"
-version = "0.5.5"
+version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "500a1dadad1e0e41e417d633b3d6d5de677c9e0d3159b94ba3348436cdb15aab"
+checksum = "bc777288799bf6786e5200325a56e4fbabba590264a4a48a0c70b16ad0cf5cd8"
dependencies = [
"proc-macro2",
"quote",
@@ -13644,56 +11534,54 @@ dependencies = [
[[package]]
name = "swc_ecma_transforms_proposal"
-version = "0.179.0"
+version = "27.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "79938ff510fc647febd8c6c3ef4143d099fdad87a223680e632623d056dae2dd"
+checksum = "39b3b34f6a28348416174912009d09994ab71c867682ec78d641a9feb3a96b4e"
dependencies = [
"either",
- "rustc-hash 1.1.0",
+ "rustc-hash 2.1.2",
"serde",
- "smallvec",
"swc_atoms",
"swc_common",
"swc_ecma_ast",
"swc_ecma_transforms_base",
"swc_ecma_transforms_classes",
- "swc_ecma_transforms_macros",
"swc_ecma_utils",
"swc_ecma_visit",
]
[[package]]
name = "swc_ecma_transforms_react"
-version = "0.191.0"
+version = "30.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "76c76d8b9792ce51401d38da0fa62158d61f6d80d16d68fe5b03ce4bf5fba383"
+checksum = "69ea0052ac23b5b9fbc85bbdb1791b36b918f9d55f594b0ed8e25babb4c32d16"
dependencies = [
- "base64 0.21.7",
- "dashmap 5.5.3",
+ "base64 0.22.1",
+ "bytes-str",
"indexmap 2.14.0",
"once_cell",
+ "rustc-hash 2.1.2",
"serde",
"sha1",
"string_enum",
- "swc_allocator",
"swc_atoms",
"swc_common",
"swc_config",
"swc_ecma_ast",
"swc_ecma_parser",
"swc_ecma_transforms_base",
- "swc_ecma_transforms_macros",
"swc_ecma_utils",
"swc_ecma_visit",
]
[[package]]
name = "swc_ecma_transforms_typescript"
-version = "0.198.1"
+version = "30.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "15455da4768f97186c40523e83600495210c11825d3a44db43383fd81eace88d"
+checksum = "3872c006ccfdcc19f1cf5c01c15915a69964ba7982c9f581cdb7e727e77b9a2c"
dependencies = [
- "ryu-js",
+ "bytes-str",
+ "rustc-hash 2.1.2",
"serde",
"swc_atoms",
"swc_common",
@@ -13706,28 +11594,28 @@ dependencies = [
[[package]]
name = "swc_ecma_utils"
-version = "0.134.2"
+version = "21.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "029eec7dd485923a75b5a45befd04510288870250270292fc2c1b3a9e7547408"
+checksum = "83259addd99ed4022aa9fc4d39428c008d3d42533769e1a005529da18cde4568"
dependencies = [
"indexmap 2.14.0",
"num_cpus",
"once_cell",
- "rustc-hash 1.1.0",
+ "par-core",
+ "rustc-hash 2.1.2",
"ryu-js",
"swc_atoms",
"swc_common",
"swc_ecma_ast",
"swc_ecma_visit",
"tracing",
- "unicode-id",
]
[[package]]
name = "swc_ecma_visit"
-version = "0.104.8"
+version = "15.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b1c6802e68e51f336e8bc9644e9ff9da75d7da9c1a6247d532f2e908aa33e81"
+checksum = "75a579aa8f9e212af521588df720ccead079c09fe5c8f61007cf724324aed3a0"
dependencies = [
"new_debug_unreachable",
"num-bigint",
@@ -13740,9 +11628,9 @@ dependencies = [
[[package]]
name = "swc_eq_ignore_macros"
-version = "0.1.4"
+version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "63db0adcff29d220c3d151c5b25c0eabe7e32dd936212b84cdaa1392e3130497"
+checksum = "c16ce73424a6316e95e09065ba6a207eba7765496fed113702278b7711d4b632"
dependencies = [
"proc-macro2",
"quote",
@@ -13751,38 +11639,44 @@ dependencies = [
[[package]]
name = "swc_macros_common"
-version = "0.3.13"
+version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f486687bfb7b5c560868f69ed2d458b880cebc9babebcb67e49f31b55c5bf847"
+checksum = "aae1efbaa74943dc5ad2a2fb16cbd78b77d7e4d63188f3c5b4df2b4dcd2faaae"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
]
+[[package]]
+name = "swc_sourcemap"
+version = "9.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de08ef00f816acdd1a58ee8a81c0e1a59eefef2093aefe5611f256fa6b64c4d7"
+dependencies = [
+ "base64-simd",
+ "bitvec",
+ "bytes-str",
+ "data-encoding",
+ "debugid",
+ "if_chain",
+ "rustc-hash 2.1.2",
+ "serde",
+ "serde_json",
+ "unicode-id-start",
+ "url",
+]
+
[[package]]
name = "swc_visit"
-version = "0.6.2"
+version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ceb044142ba2719ef9eb3b6b454fce61ab849eb696c34d190f04651955c613d"
+checksum = "62fb71484b486c185e34d2172f0eabe7f4722742aad700f426a494bb2de232a2"
dependencies = [
"either",
"new_debug_unreachable",
]
-[[package]]
-name = "swc_visit_macros"
-version = "0.5.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "92807d840959f39c60ce8a774a3f83e8193c658068e6d270dbe0a05e40e90b41"
-dependencies = [
- "Inflector",
- "proc-macro2",
- "quote",
- "swc_macros_common",
- "syn 2.0.117",
-]
-
[[package]]
name = "symlink"
version = "0.1.0"
@@ -13820,18 +11714,6 @@ dependencies = [
"futures-core",
]
-[[package]]
-name = "synstructure"
-version = "0.12.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 1.0.109",
- "unicode-xid",
-]
-
[[package]]
name = "synstructure"
version = "0.13.2"
@@ -13854,12 +11736,22 @@ dependencies = [
[[package]]
name = "sys_traits"
-version = "0.1.7"
+version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b46ac05dfbe9fd3a9703eff20e17f5b31e7b6a54daf27a421dcd56c7a27ecdd"
+checksum = "dc4707edf3196e8037ee45018d1bb1bfb233b0e4fc440fa3d3f25bc69bfdaf26"
dependencies = [
- "libc",
- "windows-sys 0.59.0",
+ "sys_traits_macros",
+]
+
+[[package]]
+name = "sys_traits_macros"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "181f22127402abcf8ee5c83ccd5b408933fec36a6095cf82cda545634692657e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
]
[[package]]
@@ -13868,7 +11760,7 @@ version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01198a2debb237c62b6826ec7081082d951f46dbb64b0e8c7649a452230d1dfc"
dependencies = [
- "bitflags 2.9.4",
+ "bitflags 2.11.1",
"byteorder",
"enum-as-inner",
"libc",
@@ -13896,7 +11788,7 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b"
dependencies = [
- "bitflags 2.9.4",
+ "bitflags 2.11.1",
"core-foundation 0.9.4",
"system-configuration-sys",
]
@@ -13920,7 +11812,7 @@ dependencies = [
"bytesize",
"lazy_static",
"libc",
- "nom 7.1.3",
+ "nom",
"time",
"winapi",
]
@@ -13955,9 +11847,9 @@ dependencies = [
"levenshtein_automata",
"log",
"lru 0.16.4",
- "lz4_flex 0.13.0",
+ "lz4_flex 0.13.1",
"measure_time",
- "memmap2 0.9.10",
+ "memmap2",
"once_cell",
"oneshot",
"rayon",
@@ -14035,7 +11927,7 @@ version = "0.25.0"
source = "git+https://github.com/windmill-labs/tantivy?rev=6ae7c70bc603b8e69e27f3240e08bd00a93fb12c#6ae7c70bc603b8e69e27f3240e08bd00a93fb12c"
dependencies = [
"fnv",
- "nom 7.1.3",
+ "nom",
"ordered-float 5.3.0",
"serde",
"serde_json",
@@ -14079,15 +11971,24 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "tar"
-version = "0.4.45"
+version = "0.4.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22692a6476a21fa75fdfc11d452fda482af402c008cdbaf3476414e122040973"
+checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840"
dependencies = [
"filetime",
"libc",
"xattr",
]
+[[package]]
+name = "temp_deno_which"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "366c5ccd670145885feb6efd6bbf2478ed236c4c3839046fcc8e2a1a84c51091"
+dependencies = [
+ "either",
+]
+
[[package]]
name = "tempfile"
version = "3.27.0"
@@ -14394,7 +12295,7 @@ dependencies = [
"bytes",
"io-uring",
"libc",
- "mio 1.2.0",
+ "mio",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
@@ -14405,16 +12306,6 @@ dependencies = [
"windows-sys 0.52.0",
]
-[[package]]
-name = "tokio-eld"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9166030f05d6bc5642bdb8f8c2be31eb3c02cd465d662bcdc2df82d4aa41a584"
-dependencies = [
- "hdrhistogram",
- "tokio",
-]
-
[[package]]
name = "tokio-graceful"
version = "0.1.6"
@@ -14439,18 +12330,6 @@ dependencies = [
"syn 2.0.117",
]
-[[package]]
-name = "tokio-metrics"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eace09241d62c98b7eeb1107d4c5c64ca3bd7da92e8c218c153ab3a78f9be112"
-dependencies = [
- "futures-util",
- "pin-project-lite",
- "tokio",
- "tokio-stream",
-]
-
[[package]]
name = "tokio-native-tls"
version = "0.3.1"
@@ -14463,50 +12342,24 @@ dependencies = [
[[package]]
name = "tokio-postgres"
-version = "0.7.11"
-source = "git+https://github.com/imor/rust-postgres?rev=20265ef38e32a06f76b6f9b678e2077fc2211f6b#20265ef38e32a06f76b6f9b678e2077fc2211f6b"
+version = "0.7.15"
+source = "git+https://github.com/MaterializeInc/rust-postgres?rev=78c1222577bb091d69bc22b1bc7ad01c14675abe#78c1222577bb091d69bc22b1bc7ad01c14675abe"
dependencies = [
"async-trait",
"byteorder",
"bytes",
- "fallible-iterator 0.2.0",
+ "fallible-iterator",
"futures-channel",
"futures-util",
"log",
"parking_lot",
"percent-encoding",
- "phf 0.11.3",
+ "phf 0.13.1",
"pin-project-lite",
- "postgres-protocol 0.6.7",
- "postgres-types 0.2.7",
- "rand 0.8.5",
- "socket2 0.5.10",
- "tokio",
- "tokio-util",
- "whoami",
-]
-
-[[package]]
-name = "tokio-postgres"
-version = "0.7.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6c95d533c83082bb6490e0189acaa0bbeef9084e60471b696ca6988cd0541fb0"
-dependencies = [
- "async-trait",
- "byteorder",
- "bytes",
- "fallible-iterator 0.2.0",
- "futures-channel",
- "futures-util",
- "log",
- "parking_lot",
- "percent-encoding",
- "phf 0.11.3",
- "pin-project-lite",
- "postgres-protocol 0.6.11",
- "postgres-types 0.2.9",
+ "postgres-protocol",
+ "postgres-types",
"rand 0.9.0",
- "socket2 0.5.10",
+ "socket2 0.6.4",
"tokio",
"tokio-util",
"whoami",
@@ -14555,9 +12408,9 @@ dependencies = [
[[package]]
name = "tokio-socks"
-version = "0.5.2"
+version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0d4770b8024672c1101b3f6733eab95b18007dbe0847a8afe341fcf79e06043f"
+checksum = "a7e2948f60dbe26b35f2c7fb74ac2854c1fddded0fe9d7548fcc674a246f7615"
dependencies = [
"either",
"futures-util",
@@ -14619,12 +12472,24 @@ dependencies = [
"futures-io",
"futures-sink",
"futures-util",
- "hashbrown 0.15.5",
"pin-project-lite",
"slab",
"tokio",
]
+[[package]]
+name = "tokio-vsock"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b319ef9394889dab2e1b4f0085b45ba11d0c79dc9d1a9d1afc057d009d0f1c7"
+dependencies = [
+ "bytes",
+ "futures",
+ "libc",
+ "tokio",
+ "vsock",
+]
+
[[package]]
name = "tokio-websockets"
version = "0.10.1"
@@ -14635,7 +12500,7 @@ dependencies = [
"bytes",
"futures-core",
"futures-sink",
- "http 1.4.0",
+ "http 1.4.1",
"httparse",
"rand 0.8.5",
"ring 0.17.14",
@@ -14669,11 +12534,11 @@ dependencies = [
[[package]]
name = "toml_datetime"
-version = "0.7.0"
+version = "1.1.1+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3"
+checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
dependencies = [
- "serde",
+ "serde_core",
]
[[package]]
@@ -14691,14 +12556,14 @@ dependencies = [
[[package]]
name = "toml_edit"
-version = "0.23.4"
+version = "0.25.12+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7211ff1b8f0d3adae1663b7da9ffe396eabe1ca25f0b0bee42b0da29a9ddce93"
+checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7"
dependencies = [
"indexmap 2.14.0",
- "toml_datetime 0.7.0",
+ "toml_datetime 1.1.1+spec-1.1.0",
"toml_parser",
- "winnow 0.7.15",
+ "winnow 1.0.3",
]
[[package]]
@@ -14707,7 +12572,7 @@ version = "1.1.2+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
dependencies = [
- "winnow 1.0.2",
+ "winnow 1.0.3",
]
[[package]]
@@ -14722,11 +12587,11 @@ dependencies = [
"base64 0.22.1",
"bytes",
"flate2",
- "h2 0.4.13",
- "http 1.4.0",
+ "h2 0.4.14",
+ "http 1.4.1",
"http-body 1.0.1",
"http-body-util",
- "hyper 1.9.0",
+ "hyper 1.10.1",
"hyper-timeout",
"hyper-util",
"percent-encoding",
@@ -14751,14 +12616,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e581ba15a835f4d9ea06c55ab1bd4dce26fc53752c69a04aac00703bfb49ba9"
dependencies = [
"async-trait",
- "axum 0.8.4",
+ "axum 0.8.9",
"base64 0.22.1",
"bytes",
- "h2 0.4.13",
- "http 1.4.0",
+ "h2 0.4.14",
+ "http 1.4.1",
"http-body 1.0.1",
"http-body-util",
- "hyper 1.9.0",
+ "hyper 1.10.1",
"hyper-timeout",
"hyper-util",
"percent-encoding",
@@ -14823,7 +12688,7 @@ dependencies = [
"axum-core 0.5.6",
"cookie",
"futures-util",
- "http 1.4.0",
+ "http 1.4.1",
"parking_lot",
"pin-project-lite",
"tower-layer",
@@ -14832,20 +12697,19 @@ dependencies = [
[[package]]
name = "tower-http"
-version = "0.6.8"
+version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
+checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
dependencies = [
"async-compression",
"base64 0.22.1",
- "bitflags 2.9.4",
+ "bitflags 2.11.1",
"bytes",
"futures-core",
"futures-util",
- "http 1.4.0",
+ "http 1.4.1",
"http-body 1.0.1",
"http-body-util",
- "iri-string",
"mime",
"pin-project-lite",
"tokio",
@@ -14854,6 +12718,7 @@ dependencies = [
"tower-layer",
"tower-service",
"tracing",
+ "url",
]
[[package]]
@@ -15077,7 +12942,7 @@ dependencies = [
"byteorder",
"bytes",
"data-encoding",
- "http 1.4.0",
+ "http 1.4.1",
"httparse",
"log",
"native-tls",
@@ -15099,7 +12964,7 @@ dependencies = [
"byteorder",
"bytes",
"data-encoding",
- "http 1.4.0",
+ "http 1.4.1",
"httparse",
"log",
"native-tls",
@@ -15109,29 +12974,12 @@ dependencies = [
"utf-8",
]
-[[package]]
-name = "twox-hash"
-version = "1.6.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675"
-dependencies = [
- "cfg-if",
- "rand 0.8.5",
- "static_assertions",
-]
-
[[package]]
name = "twox-hash"
version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c"
-[[package]]
-name = "typed-arena"
-version = "2.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a"
-
[[package]]
name = "typed-path"
version = "0.12.3"
@@ -15146,15 +12994,15 @@ checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
[[package]]
name = "typenum"
-version = "1.20.0"
+version = "1.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de"
+checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
[[package]]
name = "typetag"
-version = "0.2.21"
+version = "0.2.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be2212c8a9b9bcfca32024de14998494cf9a5dfa59ea1b829de98bac374b86bf"
+checksum = "c5a897b12c6c1151ad0b138b8db50252dc301f93bc3b027db05eec82aeed298c"
dependencies = [
"erased-serde",
"inventory",
@@ -15165,9 +13013,9 @@ dependencies = [
[[package]]
name = "typetag-impl"
-version = "0.2.21"
+version = "0.2.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "27a7a9b72ba121f6f1f6c3632b85604cac41aedb5ddc70accbebb6cac83de846"
+checksum = "cf808357c6ed7e13ba0f3277ec8d8f21b2d501274895104263985330c726c1c5"
dependencies = [
"proc-macro2",
"quote",
@@ -15267,12 +13115,6 @@ version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
-[[package]]
-name = "unicode-id"
-version = "0.3.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70ba288e709927c043cbe476718d37be306be53fb1fafecd0dbe36d072be2580"
-
[[package]]
name = "unicode-id-start"
version = "1.4.0"
@@ -15373,7 +13215,7 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
dependencies = [
- "crypto-common 0.1.7",
+ "crypto-common",
"subtle",
]
@@ -15484,66 +13326,38 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
-version = "1.18.1"
+version = "1.23.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2"
+checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7"
dependencies = [
- "getrandom 0.3.4",
+ "getrandom 0.4.2",
"js-sys",
- "serde",
+ "serde_core",
"wasm-bindgen",
]
[[package]]
name = "v8"
-version = "130.0.7"
+version = "137.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a511192602f7b435b0a241c1947aa743eb7717f20a9195f4b5e8ed1952e01db1"
+checksum = "33995a1fee055ff743281cde33a41f0d618ee0bdbe8bdf6859e11864499c2595"
dependencies = [
- "bindgen 0.70.1",
- "bitflags 2.9.4",
+ "bindgen 0.71.1",
+ "bitflags 2.11.1",
"fslock",
"gzip-header",
"home",
- "miniz_oxide 0.7.4",
- "once_cell",
+ "miniz_oxide",
"paste",
"which 6.0.3",
]
-[[package]]
-name = "v8_valueserializer"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97599c400fc79925922b58303e98fcb8fa88f573379a08ddb652e72cbd2e70f6"
-dependencies = [
- "bitflags 2.9.4",
- "encoding_rs",
- "indexmap 2.14.0",
- "num-bigint",
- "serde",
- "thiserror 1.0.69",
- "wtf8",
-]
-
[[package]]
name = "valuable"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
-[[package]]
-name = "value-trait"
-version = "0.10.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9170e001f458781e92711d2ad666110f153e4e50bfd5cbd02db6547625714187"
-dependencies = [
- "float-cmp",
- "halfbrown",
- "itoa",
- "ryu",
-]
-
[[package]]
name = "vcpkg"
version = "0.2.15"
@@ -15562,6 +13376,16 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64"
+[[package]]
+name = "vsock"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ba782755fc073877e567c2253c0be48e4aa9a254c232d36d3985dfae0bd5205"
+dependencies = [
+ "libc",
+ "nix 0.31.3",
+]
+
[[package]]
name = "vte"
version = "0.14.1"
@@ -15760,11 +13584,11 @@ dependencies = [
[[package]]
name = "wasm_dep_analyzer"
-version = "0.2.0"
+version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2eeee3bdea6257cc36d756fa745a70f9d393571e47d69e0ed97581676a5369ca"
+checksum = "e51cf5f08b357e64cd7642ab4bbeb11aecab9e15520692129624fb9908b8df2c"
dependencies = [
- "deno_error",
+ "deno_error 0.6.1",
"thiserror 2.0.18",
]
@@ -15774,7 +13598,7 @@ version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
dependencies = [
- "bitflags 2.9.4",
+ "bitflags 2.11.1",
"hashbrown 0.15.5",
"indexmap 2.14.0",
"semver 1.0.28",
@@ -15815,12 +13639,15 @@ dependencies = [
]
[[package]]
-name = "webpki-root-certs"
-version = "0.26.11"
+name = "web-transport-proto"
+version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "75c7f0ef91146ebfb530314f5f1d24528d7f0767efbfd31dce919275413e393e"
+checksum = "974fa1e325e6cc5327de8887f189a441fcff4f8eedcd31ec87f0ef0cc5283fbc"
dependencies = [
- "webpki-root-certs 1.0.7",
+ "bytes",
+ "http 1.4.1",
+ "thiserror 2.0.18",
+ "url",
]
[[package]]
@@ -15850,89 +13677,6 @@ dependencies = [
"rustls-pki-types",
]
-[[package]]
-name = "wgpu-core"
-version = "0.21.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d50819ab545b867d8a454d1d756b90cd5f15da1f2943334ca314af10583c9d39"
-dependencies = [
- "arrayvec",
- "bit-vec 0.6.3",
- "bitflags 2.9.4",
- "cfg_aliases 0.1.1",
- "codespan-reporting",
- "document-features",
- "indexmap 2.14.0",
- "log",
- "naga",
- "once_cell",
- "parking_lot",
- "profiling",
- "raw-window-handle",
- "ron",
- "rustc-hash 1.1.0",
- "serde",
- "smallvec",
- "thiserror 1.0.69",
- "web-sys",
- "wgpu-hal",
- "wgpu-types",
-]
-
-[[package]]
-name = "wgpu-hal"
-version = "0.21.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "172e490a87295564f3fcc0f165798d87386f6231b04d4548bca458cbbfd63222"
-dependencies = [
- "android_system_properties",
- "arrayvec",
- "ash",
- "bit-set 0.5.3",
- "bitflags 2.9.4",
- "block",
- "cfg_aliases 0.1.1",
- "core-graphics-types",
- "d3d12",
- "glow",
- "glutin_wgl_sys",
- "gpu-alloc",
- "gpu-descriptor",
- "js-sys",
- "khronos-egl",
- "libc",
- "libloading 0.8.9",
- "log",
- "metal",
- "naga",
- "ndk-sys",
- "objc",
- "once_cell",
- "parking_lot",
- "profiling",
- "range-alloc",
- "raw-window-handle",
- "rustc-hash 1.1.0",
- "smallvec",
- "thiserror 1.0.69",
- "wasm-bindgen",
- "web-sys",
- "wgpu-types",
- "winapi",
-]
-
-[[package]]
-name = "wgpu-types"
-version = "0.20.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1353d9a46bff7f955a680577f34c69122628cc2076e1d6f3a9be6ef00ae793ef"
-dependencies = [
- "bitflags 2.9.4",
- "js-sys",
- "serde",
- "web-sys",
-]
-
[[package]]
name = "which"
version = "4.4.2"
@@ -16020,14 +13764,14 @@ dependencies = [
[[package]]
name = "windmill"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"async-nats",
"aws-config",
"aws-credential-types",
"aws-sdk-sqs",
- "axum 0.8.4",
+ "axum 0.8.9",
"base64 0.22.1",
"chrono",
"constant_time_eq 0.3.1",
@@ -16054,7 +13798,7 @@ dependencies = [
"sha2 0.10.9",
"sql-builder",
"sqlx",
- "strum 0.27.2",
+ "strum",
"tar",
"tempfile",
"tikv-jemalloc-ctl",
@@ -16101,21 +13845,30 @@ dependencies = [
[[package]]
name = "windmill-ai"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
+ "async-stream",
"async-trait",
"aws-config",
"aws-credential-types",
+ "aws-sdk-bedrock",
"aws-sdk-bedrockruntime",
"aws-smithy-types",
"base64 0.22.1",
+ "bytes",
+ "eventsource-stream",
+ "futures",
+ "http 1.4.1",
"lazy_static",
+ "mime_guess",
"reqwest 0.13.1",
"serde",
"serde_json",
"sqlx",
"tokio",
+ "tokio-stream",
"tracing",
+ "ulid",
"uuid",
"windmill-common",
"windmill-mcp",
@@ -16125,9 +13878,9 @@ dependencies = [
[[package]]
name = "windmill-alerting"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
- "axum 0.8.4",
+ "axum 0.8.9",
"chrono",
"serde",
"serde_json",
@@ -16138,7 +13891,7 @@ dependencies = [
[[package]]
name = "windmill-api"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"argon2",
@@ -16147,14 +13900,9 @@ dependencies = [
"async-stream",
"async-trait",
"async_zip",
- "aws-config",
- "aws-credential-types",
- "aws-sdk-bedrock",
- "aws-sdk-bedrockruntime",
"aws-sdk-config",
"aws-sigv4",
- "aws-smithy-types",
- "axum 0.8.4",
+ "axum 0.8.9",
"base32",
"base64 0.22.1",
"bytes",
@@ -16163,7 +13911,7 @@ dependencies = [
"const_format",
"cookie",
"cron",
- "dashmap 6.1.0",
+ "dashmap",
"datafusion",
"ed25519-dalek",
"eventsource-stream",
@@ -16171,9 +13919,9 @@ dependencies = [
"futures",
"git-version",
"hex",
- "hmac 0.12.1",
- "http 1.4.0",
- "hyper 1.9.0",
+ "hmac",
+ "http 1.4.1",
+ "hyper 1.10.1",
"indexmap 2.14.0",
"itertools 0.14.0",
"jsonwebtoken 8.3.0",
@@ -16187,7 +13935,7 @@ dependencies = [
"openidconnect",
"openssl",
"pin-project",
- "postgres-native-tls 0.5.1",
+ "postgres-native-tls 0.5.3",
"prometheus",
"quick_cache",
"rand 0.9.0",
@@ -16204,13 +13952,13 @@ dependencies = [
"sha2 0.10.9",
"sql-builder",
"sqlx",
- "strum 0.27.2",
+ "strum",
"tar",
"tempfile",
"time",
"tokio",
"tokio-native-tls",
- "tokio-postgres 0.7.13",
+ "tokio-postgres",
"tokio-stream",
"tokio-util",
"tower 0.5.3",
@@ -16281,12 +14029,12 @@ dependencies = [
[[package]]
name = "windmill-api-agent-workers"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
- "axum 0.8.4",
+ "axum 0.8.9",
"chrono",
- "http 1.4.0",
- "hyper 1.9.0",
+ "http 1.4.1",
+ "hyper 1.10.1",
"lazy_static",
"quick_cache",
"serde",
@@ -16304,9 +14052,9 @@ dependencies = [
[[package]]
name = "windmill-api-assets"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
- "axum 0.8.4",
+ "axum 0.8.9",
"chrono",
"serde",
"serde_json",
@@ -16317,12 +14065,12 @@ dependencies = [
[[package]]
name = "windmill-api-auth"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
- "axum 0.8.4",
+ "axum 0.8.9",
"chrono",
- "http 1.4.0",
+ "http 1.4.1",
"itertools 0.14.0",
"jsonwebtoken 8.3.0",
"lazy_static",
@@ -16343,7 +14091,7 @@ dependencies = [
[[package]]
name = "windmill-api-client"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"reqwest 0.12.28",
"serde",
@@ -16353,9 +14101,9 @@ dependencies = [
[[package]]
name = "windmill-api-configs"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
- "axum 0.8.4",
+ "axum 0.8.9",
"chrono",
"itertools 0.14.0",
"serde",
@@ -16370,9 +14118,9 @@ dependencies = [
[[package]]
name = "windmill-api-debug"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
- "axum 0.8.4",
+ "axum 0.8.9",
"base64 0.22.1",
"chrono",
"ed25519-dalek",
@@ -16392,10 +14140,10 @@ dependencies = [
[[package]]
name = "windmill-api-embeddings"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
- "axum 0.8.4",
+ "axum 0.8.9",
"candle-core",
"candle-nn",
"candle-transformers",
@@ -16415,9 +14163,9 @@ dependencies = [
[[package]]
name = "windmill-api-flow-conversations"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
- "axum 0.8.4",
+ "axum 0.8.9",
"chrono",
"serde",
"sql-builder",
@@ -16431,11 +14179,11 @@ dependencies = [
[[package]]
name = "windmill-api-flows"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
- "axum 0.8.4",
+ "axum 0.8.9",
"chrono",
- "hyper 1.9.0",
+ "hyper 1.10.1",
"serde",
"serde_json",
"sql-builder",
@@ -16452,9 +14200,9 @@ dependencies = [
[[package]]
name = "windmill-api-groups"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
- "axum 0.8.4",
+ "axum 0.8.9",
"chrono",
"globset",
"lazy_static",
@@ -16473,9 +14221,9 @@ dependencies = [
[[package]]
name = "windmill-api-inputs"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
- "axum 0.8.4",
+ "axum 0.8.9",
"chrono",
"serde",
"serde_json",
@@ -16487,14 +14235,14 @@ dependencies = [
[[package]]
name = "windmill-api-integration-tests"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"async-nats",
"aws-config",
"aws-credential-types",
"aws-sdk-sqs",
- "axum 0.8.4",
+ "axum 0.8.9",
"base64 0.22.1",
"futures",
"rand 0.9.0",
@@ -16519,14 +14267,14 @@ dependencies = [
[[package]]
name = "windmill-api-jobs"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
- "axum 0.8.4",
+ "axum 0.8.9",
"base64 0.22.1",
"chrono",
- "http 1.4.0",
- "hyper 1.9.0",
+ "http 1.4.1",
+ "hyper 1.10.1",
"lazy_static",
"serde",
"serde_json",
@@ -16544,9 +14292,9 @@ dependencies = [
[[package]]
name = "windmill-api-npm-proxy"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
- "axum 0.8.4",
+ "axum 0.8.9",
"flate2",
"reqwest 0.13.1",
"serde",
@@ -16562,11 +14310,11 @@ dependencies = [
[[package]]
name = "windmill-api-openapi"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
- "axum 0.8.4",
- "http 1.4.0",
+ "axum 0.8.9",
+ "http 1.4.1",
"indexmap 2.14.0",
"itertools 0.14.0",
"lazy_static",
@@ -16584,9 +14332,9 @@ dependencies = [
[[package]]
name = "windmill-api-schedule"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
- "axum 0.8.4",
+ "axum 0.8.9",
"chrono",
"chrono-tz",
"serde",
@@ -16604,13 +14352,13 @@ dependencies = [
[[package]]
name = "windmill-api-scripts"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
- "axum 0.8.4",
+ "axum 0.8.9",
"chrono",
"futures",
- "http 1.4.0",
- "hyper 1.9.0",
+ "http 1.4.1",
+ "hyper 1.10.1",
"itertools 0.14.0",
"lazy_static",
"quick_cache",
@@ -16634,10 +14382,10 @@ dependencies = [
[[package]]
name = "windmill-api-settings"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
- "axum 0.8.4",
+ "axum 0.8.9",
"base64 0.22.1",
"bytes",
"chrono",
@@ -16662,7 +14410,7 @@ dependencies = [
[[package]]
name = "windmill-api-sse"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"lazy_static",
"serde",
@@ -16674,14 +14422,14 @@ dependencies = [
[[package]]
name = "windmill-api-users"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"argon2",
- "axum 0.8.4",
+ "axum 0.8.9",
"chrono",
- "dashmap 6.1.0",
- "http 1.4.0",
- "hyper 1.9.0",
+ "dashmap",
+ "http 1.4.1",
+ "hyper 1.10.1",
"lazy_static",
"serde",
"serde_json",
@@ -16699,9 +14447,9 @@ dependencies = [
[[package]]
name = "windmill-api-workers"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
- "axum 0.8.4",
+ "axum 0.8.9",
"chrono",
"serde",
"serde_json",
@@ -16713,13 +14461,13 @@ dependencies = [
[[package]]
name = "windmill-api-workspaces"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
- "axum 0.8.4",
+ "axum 0.8.9",
"chrono",
"hex",
- "http 1.4.0",
- "hyper 1.9.0",
+ "http 1.4.1",
+ "hyper 1.10.1",
"lazy_static",
"magic-crypt",
"regex",
@@ -16727,7 +14475,7 @@ dependencies = [
"serde_json",
"sha2 0.10.9",
"sqlx",
- "strum 0.27.2",
+ "strum",
"tokio",
"tracing",
"uuid",
@@ -16746,7 +14494,7 @@ dependencies = [
[[package]]
name = "windmill-audit"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"chrono",
"lazy_static",
@@ -16760,10 +14508,10 @@ dependencies = [
[[package]]
name = "windmill-autoscaling"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
- "axum 0.8.4",
+ "axum 0.8.9",
"k8s-openapi",
"kube",
"serde",
@@ -16779,7 +14527,7 @@ dependencies = [
[[package]]
name = "windmill-common"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"aes-gcm",
"aho-corasick",
@@ -16793,10 +14541,10 @@ dependencies = [
"aws-sdk-secretsmanager",
"aws-sdk-sts",
"aws-smithy-types-convert",
- "axum 0.8.4",
+ "axum 0.8.9",
"backon",
"base64 0.22.1",
- "bitflags 2.9.4",
+ "bitflags 2.11.1",
"bytes",
"chrono",
"chrono-tz",
@@ -16805,7 +14553,7 @@ dependencies = [
"crc",
"cron",
"croner",
- "dashmap 6.1.0",
+ "dashmap",
"datafusion",
"equivalent",
"futures",
@@ -16814,8 +14562,8 @@ dependencies = [
"git-version",
"globset",
"hex",
- "hmac 0.12.1",
- "hyper 1.9.0",
+ "hmac",
+ "hyper 1.10.1",
"indexmap 2.14.0",
"itertools 0.14.0",
"jsonwebtoken 8.3.0",
@@ -16833,7 +14581,7 @@ dependencies = [
"pep440_rs",
"phf 0.11.3",
"pin-project-lite",
- "postgres-native-tls 0.5.1",
+ "postgres-native-tls 0.5.3",
"prometheus",
"quick_cache",
"rand 0.9.0",
@@ -16850,8 +14598,8 @@ dependencies = [
"sha2 0.10.9",
"size",
"sqlx",
- "strum 0.27.2",
- "strum_macros 0.27.2",
+ "strum",
+ "strum_macros",
"sysinfo",
"systemstat",
"tar",
@@ -16859,7 +14607,7 @@ dependencies = [
"thiserror 2.0.18",
"tikv-jemalloc-ctl",
"tokio",
- "tokio-postgres 0.7.13",
+ "tokio-postgres",
"tokio-stream",
"tokio-util",
"tonic 0.13.1",
@@ -16880,7 +14628,7 @@ dependencies = [
[[package]]
name = "windmill-dep-map"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"chrono",
"itertools 0.14.0",
@@ -16899,7 +14647,7 @@ dependencies = [
[[package]]
name = "windmill-git-sync"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"regex",
"serde",
@@ -16914,7 +14662,7 @@ dependencies = [
[[package]]
name = "windmill-indexer"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"astral-tokio-tar",
@@ -16938,7 +14686,7 @@ dependencies = [
[[package]]
name = "windmill-jseval"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"futures",
@@ -16955,7 +14703,7 @@ dependencies = [
[[package]]
name = "windmill-macros"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"itertools 0.14.0",
"lazy_static",
@@ -16971,13 +14719,13 @@ dependencies = [
[[package]]
name = "windmill-mcp"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"async-trait",
"chrono",
"futures",
- "http 1.4.0",
+ "http 1.4.1",
"oauth2",
"reqwest 0.12.28",
"rmcp",
@@ -16992,16 +14740,16 @@ dependencies = [
[[package]]
name = "windmill-native-triggers"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"async-trait",
- "axum 0.8.4",
+ "axum 0.8.9",
"backon",
"base64 0.22.1",
"chrono",
- "hmac 0.12.1",
- "http 1.4.0",
+ "hmac",
+ "http 1.4.1",
"itertools 0.14.0",
"lazy_static",
"reqwest 0.13.1",
@@ -17009,7 +14757,7 @@ dependencies = [
"serde_json",
"sha2 0.10.9",
"sqlx",
- "strum 0.27.2",
+ "strum",
"tokio",
"tracing",
"urlencoding",
@@ -17023,16 +14771,16 @@ dependencies = [
[[package]]
name = "windmill-oauth"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"arc-swap",
"async-oauth2",
- "axum 0.8.4",
+ "axum 0.8.9",
"base64 0.22.1",
"chrono",
"hex",
- "hmac 0.12.1",
+ "hmac",
"itertools 0.14.0",
"lazy_static",
"reqwest 0.12.28",
@@ -17048,7 +14796,7 @@ dependencies = [
[[package]]
name = "windmill-object-store"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"async-stream",
@@ -17057,7 +14805,7 @@ dependencies = [
"aws-credential-types",
"aws-sdk-sts",
"aws-smithy-types-convert",
- "axum 0.8.4",
+ "axum 0.8.9",
"bytes",
"chrono",
"datafusion",
@@ -17082,7 +14830,7 @@ dependencies = [
[[package]]
name = "windmill-operator"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"futures",
@@ -17100,7 +14848,7 @@ dependencies = [
[[package]]
name = "windmill-parser"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"convert_case 0.6.0",
"serde",
@@ -17109,7 +14857,7 @@ dependencies = [
[[package]]
name = "windmill-parser-bash"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"lazy_static",
@@ -17121,7 +14869,7 @@ dependencies = [
[[package]]
name = "windmill-parser-csharp"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"serde_json",
@@ -17133,7 +14881,7 @@ dependencies = [
[[package]]
name = "windmill-parser-go"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"gosyn",
@@ -17145,7 +14893,7 @@ dependencies = [
[[package]]
name = "windmill-parser-graphql"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"lazy_static",
@@ -17157,7 +14905,7 @@ dependencies = [
[[package]]
name = "windmill-parser-java"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"serde_json",
@@ -17169,7 +14917,7 @@ dependencies = [
[[package]]
name = "windmill-parser-nu"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"nu-parser",
@@ -17180,7 +14928,7 @@ dependencies = [
[[package]]
name = "windmill-parser-php"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"itertools 0.14.0",
@@ -17191,7 +14939,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"itertools 0.14.0",
@@ -17203,7 +14951,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py-asset"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"rustpython-ast",
@@ -17214,7 +14962,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py-imports"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"async-recursion",
@@ -17236,7 +14984,7 @@ dependencies = [
[[package]]
name = "windmill-parser-r"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"serde_json",
@@ -17248,7 +14996,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ruby"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"lazy_static",
@@ -17262,7 +15010,7 @@ dependencies = [
[[package]]
name = "windmill-parser-rust"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"convert_case 0.6.0",
@@ -17279,7 +15027,7 @@ dependencies = [
[[package]]
name = "windmill-parser-sql"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"lazy_static",
@@ -17292,7 +15040,7 @@ dependencies = [
[[package]]
name = "windmill-parser-sql-asset"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"serde",
@@ -17304,7 +15052,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ts"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"lazy_static",
@@ -17322,7 +15070,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ts-asset"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"serde-wasm-bindgen",
@@ -17338,7 +15086,7 @@ dependencies = [
[[package]]
name = "windmill-parser-wac"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"rustpython-ast",
@@ -17354,7 +15102,7 @@ dependencies = [
[[package]]
name = "windmill-parser-yaml"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"serde",
@@ -17365,25 +15113,26 @@ dependencies = [
[[package]]
name = "windmill-queue"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"async-recursion",
- "axum 0.8.4",
+ "axum 0.8.9",
"backon",
"chrono",
"chrono-tz",
"cron",
- "dashmap 6.1.0",
+ "dashmap",
"futures",
"futures-core",
"hex",
- "hmac 0.12.1",
+ "hmac",
"itertools 0.14.0",
"lazy_static",
"once_cell",
"prometheus",
"quick_cache",
+ "rand 0.9.0",
"regex",
"reqwest 0.13.1",
"serde",
@@ -17402,19 +15151,19 @@ dependencies = [
[[package]]
name = "windmill-runtime-nativets"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"const_format",
"deno_ast",
"deno_console",
"deno_core",
- "deno_error",
+ "deno_error 0.6.1",
"deno_fetch",
+ "deno_fs",
"deno_io",
"deno_net",
"deno_permissions",
- "deno_runtime",
"deno_telemetry",
"deno_tls",
"deno_url",
@@ -17440,7 +15189,7 @@ dependencies = [
[[package]]
name = "windmill-sql-datatype-parser-wasm"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"getrandom 0.3.4",
"wasm-bindgen",
@@ -17451,15 +15200,15 @@ dependencies = [
[[package]]
name = "windmill-store"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"async-recursion",
- "axum 0.8.4",
+ "axum 0.8.9",
"chrono",
"futures",
- "http 1.4.0",
- "hyper 1.9.0",
+ "http 1.4.1",
+ "hyper 1.10.1",
"lazy_static",
"quick_cache",
"reqwest 0.13.1",
@@ -17481,11 +15230,11 @@ dependencies = [
[[package]]
name = "windmill-test-utils"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"async-trait",
- "axum 0.8.4",
+ "axum 0.8.9",
"chrono",
"futures",
"serde",
@@ -17505,14 +15254,14 @@ dependencies = [
[[package]]
name = "windmill-trigger"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"async-trait",
- "axum 0.8.4",
+ "axum 0.8.9",
"chrono",
- "http 1.4.0",
- "hyper 1.9.0",
+ "http 1.4.1",
+ "hyper 1.10.1",
"itertools 0.14.0",
"lazy_static",
"rand 0.9.0",
@@ -17538,17 +15287,17 @@ dependencies = [
[[package]]
name = "windmill-trigger-azure"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"async-trait",
- "axum 0.8.4",
+ "axum 0.8.9",
"base64 0.22.1",
"bytes",
"chrono",
"constant_time_eq 0.3.1",
"hex",
- "http 1.4.0",
+ "http 1.4.1",
"itertools 0.14.0",
"lazy_static",
"quick_cache",
@@ -17571,11 +15320,11 @@ dependencies = [
[[package]]
name = "windmill-trigger-email"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"async-trait",
- "axum 0.8.4",
+ "axum 0.8.9",
"base64 0.22.1",
"lazy_static",
"regex",
@@ -17591,17 +15340,17 @@ dependencies = [
[[package]]
name = "windmill-trigger-gcp"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"async-trait",
- "axum 0.8.4",
+ "axum 0.8.9",
"base64 0.22.1",
"bytes",
"chrono",
"google-cloud-googleapis",
"google-cloud-pubsub",
- "http 1.4.0",
+ "http 1.4.1",
"itertools 0.14.0",
"jsonwebtoken 8.3.0",
"lazy_static",
@@ -17625,19 +15374,19 @@ dependencies = [
[[package]]
name = "windmill-trigger-http"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"async-trait",
- "axum 0.8.4",
+ "axum 0.8.9",
"base64 0.22.1",
"chrono",
"constant_time_eq 0.3.1",
"futures",
"hex",
- "hmac 0.12.1",
- "http 1.4.0",
- "hyper 1.9.0",
+ "hmac",
+ "http 1.4.1",
+ "hyper 1.10.1",
"itertools 0.14.0",
"lazy_static",
"matchit 0.7.3",
@@ -17661,11 +15410,11 @@ dependencies = [
[[package]]
name = "windmill-trigger-kafka"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"async-trait",
- "axum 0.8.4",
+ "axum 0.8.9",
"base64 0.22.1",
"itertools 0.14.0",
"rdkafka",
@@ -17684,11 +15433,11 @@ dependencies = [
[[package]]
name = "windmill-trigger-mqtt"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"async-trait",
- "axum 0.8.4",
+ "axum 0.8.9",
"base64 0.22.1",
"bytes",
"itertools 0.14.0",
@@ -17708,12 +15457,12 @@ dependencies = [
[[package]]
name = "windmill-trigger-nats"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"async-nats",
"async-trait",
- "axum 0.8.4",
+ "axum 0.8.9",
"base64 0.22.1",
"itertools 0.14.0",
"nkeys",
@@ -17732,11 +15481,11 @@ dependencies = [
[[package]]
name = "windmill-trigger-postgres"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"async-trait",
- "axum 0.8.4",
+ "axum 0.8.9",
"byteorder",
"bytes",
"chrono",
@@ -17745,7 +15494,7 @@ dependencies = [
"lazy_static",
"native-tls",
"pg_escape",
- "postgres-native-tls 0.5.0",
+ "postgres-native-tls 0.5.2",
"quick_cache",
"rand 0.9.0",
"rust_decimal",
@@ -17754,7 +15503,7 @@ dependencies = [
"sqlx",
"thiserror 2.0.18",
"tokio",
- "tokio-postgres 0.7.11",
+ "tokio-postgres",
"tokio-stream",
"tracing",
"uuid",
@@ -17767,7 +15516,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-sqs"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"async-trait",
@@ -17776,7 +15525,7 @@ dependencies = [
"aws-sdk-sqs",
"aws-sdk-sts",
"aws-smithy-types",
- "axum 0.8.4",
+ "axum 0.8.9",
"backon",
"chrono",
"itertools 0.14.0",
@@ -17795,13 +15544,14 @@ dependencies = [
[[package]]
name = "windmill-trigger-websocket"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"async-trait",
- "axum 0.8.4",
+ "axum 0.8.9",
+ "base64 0.22.1",
"futures",
- "http 1.4.0",
+ "http 1.4.1",
"itertools 0.14.0",
"serde",
"serde_json",
@@ -17809,6 +15559,7 @@ dependencies = [
"tokio",
"tokio-tungstenite 0.24.0",
"tracing",
+ "url",
"windmill-api-auth",
"windmill-common",
"windmill-git-sync",
@@ -17818,10 +15569,10 @@ dependencies = [
[[package]]
name = "windmill-types"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
- "bitflags 2.9.4",
+ "bitflags 2.11.1",
"chrono",
"hex",
"itertools 0.14.0",
@@ -17829,7 +15580,7 @@ dependencies = [
"serde",
"serde_json",
"sqlx",
- "strum 0.27.2",
+ "strum",
"tracing",
"uuid",
"windmill-parser",
@@ -17837,18 +15588,14 @@ dependencies = [
[[package]]
name = "windmill-worker"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"async-once-cell",
"async-recursion",
"async-stream",
"async-trait",
- "aws-config",
- "aws-credential-types",
- "aws-sdk-bedrockruntime",
- "aws-smithy-types",
- "axum 0.8.4",
+ "axum 0.8.9",
"backon",
"base64 0.22.1",
"bit-vec 0.6.3",
@@ -17865,7 +15612,7 @@ dependencies = [
"gcp_auth",
"git-version",
"hex",
- "hmac 0.12.1",
+ "hmac",
"hudsucker",
"hyper-http-proxy",
"hyper-tls",
@@ -17874,7 +15621,7 @@ dependencies = [
"jsonwebtoken 8.3.0",
"lazy_static",
"libffi-sys",
- "libloading 0.8.9",
+ "libloading",
"mappable-rc",
"mime_guess",
"mysql_async",
@@ -17886,15 +15633,17 @@ dependencies = [
"oracle",
"pem 3.0.6",
"pep440_rs",
- "postgres-native-tls 0.5.1",
+ "postgres-native-tls 0.5.3",
"process-wrap",
"prometheus",
"prost",
+ "quick_cache",
"rand 0.9.0",
"rcgen",
"regex",
"reqwest 0.13.1",
"reqwest-middleware",
+ "rsa",
"rust_decimal",
"serde",
"serde_json",
@@ -17904,7 +15653,7 @@ dependencies = [
"tempfile",
"tiberius",
"tokio",
- "tokio-postgres 0.7.13",
+ "tokio-postgres",
"tokio-stream",
"tokio-util",
"tracing",
@@ -17943,13 +15692,13 @@ dependencies = [
"windmill-types",
"windmill-worker-volumes",
"windows 0.61.3",
- "x509-parser 0.16.0",
+ "x509-parser",
"yaml-rust",
]
[[package]]
name = "windmill-worker-volumes"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"bytes",
"futures",
@@ -18200,7 +15949,7 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d24d6bcc7f734a4091ecf8d7a64c5f7d7066f45585c1861eba06449909609c8a"
dependencies = [
- "bitflags 2.9.4",
+ "bitflags 2.11.1",
"widestring",
"windows-sys 0.52.0",
]
@@ -18549,9 +16298,12 @@ dependencies = [
[[package]]
name = "winnow"
-version = "1.0.2"
+version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0"
+checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1"
+dependencies = [
+ "memchr",
+]
[[package]]
name = "winsafe"
@@ -18581,7 +16333,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
dependencies = [
"anyhow",
- "heck 0.5.0",
+ "heck",
"wit-parser",
]
@@ -18592,7 +16344,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
dependencies = [
"anyhow",
- "heck 0.5.0",
+ "heck",
"indexmap 2.14.0",
"prettyplease",
"syn 2.0.117",
@@ -18623,7 +16375,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
dependencies = [
"anyhow",
- "bitflags 2.9.4",
+ "bitflags 2.11.1",
"indexmap 2.14.0",
"log",
"serde",
@@ -18659,12 +16411,6 @@ version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
-[[package]]
-name = "wtf8"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c01ae8492c38f52376efd3a17d0994b6bcf3df1e39c0226d458b7d81670b2a06"
-
[[package]]
name = "wyz"
version = "0.5.1"
@@ -18674,47 +16420,18 @@ dependencies = [
"tap",
]
-[[package]]
-name = "x25519-dalek"
-version = "2.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277"
-dependencies = [
- "curve25519-dalek",
- "rand_core 0.6.4",
- "serde",
- "zeroize",
-]
-
-[[package]]
-name = "x509-parser"
-version = "0.15.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7069fba5b66b9193bd2c5d3d4ff12b839118f6bcbef5328efafafb5395cf63da"
-dependencies = [
- "asn1-rs 0.5.2",
- "data-encoding",
- "der-parser 8.2.0",
- "lazy_static",
- "nom 7.1.3",
- "oid-registry 0.6.1",
- "rusticata-macros",
- "thiserror 1.0.69",
- "time",
-]
-
[[package]]
name = "x509-parser"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69"
dependencies = [
- "asn1-rs 0.6.2",
+ "asn1-rs",
"data-encoding",
- "der-parser 9.0.0",
+ "der-parser",
"lazy_static",
- "nom 7.1.3",
- "oid-registry 0.7.1",
+ "nom",
+ "oid-registry",
"ring 0.17.14",
"rusticata-macros",
"thiserror 1.0.69",
@@ -18731,12 +16448,6 @@ dependencies = [
"rustix 1.1.4",
]
-[[package]]
-name = "xml-rs"
-version = "0.8.28"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f"
-
[[package]]
name = "xmlparser"
version = "0.13.6"
@@ -18776,18 +16487,6 @@ dependencies = [
"time",
]
-[[package]]
-name = "yoke"
-version = "0.7.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40"
-dependencies = [
- "serde",
- "stable_deref_trait",
- "yoke-derive 0.7.5",
- "zerofrom",
-]
-
[[package]]
name = "yoke"
version = "0.8.2"
@@ -18795,22 +16494,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca"
dependencies = [
"stable_deref_trait",
- "yoke-derive 0.8.2",
+ "yoke-derive",
"zerofrom",
]
-[[package]]
-name = "yoke-derive"
-version = "0.7.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.117",
- "synstructure 0.13.2",
-]
-
[[package]]
name = "yoke-derive"
version = "0.8.2"
@@ -18820,23 +16507,23 @@ dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
- "synstructure 0.13.2",
+ "synstructure",
]
[[package]]
name = "zerocopy"
-version = "0.8.48"
+version = "0.8.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"
+checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
-version = "0.8.48"
+version = "0.8.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
+checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639"
dependencies = [
"proc-macro2",
"quote",
@@ -18845,9 +16532,9 @@ dependencies = [
[[package]]
name = "zerofrom"
-version = "0.1.7"
+version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df"
+checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
dependencies = [
"zerofrom-derive",
]
@@ -18861,7 +16548,7 @@ dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
- "synstructure 0.13.2",
+ "synstructure",
]
[[package]]
@@ -18869,20 +16556,6 @@ name = "zeroize"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
-dependencies = [
- "zeroize_derive",
-]
-
-[[package]]
-name = "zeroize_derive"
-version = "1.4.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.117",
-]
[[package]]
name = "zerotrie"
@@ -18891,7 +16564,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf"
dependencies = [
"displaydoc",
- "yoke 0.8.2",
+ "yoke",
"zerofrom",
]
@@ -18901,7 +16574,7 @@ version = "0.11.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239"
dependencies = [
- "yoke 0.8.2",
+ "yoke",
"zerofrom",
"zerovec-derive",
]
diff --git a/backend/Cargo.toml b/backend/Cargo.toml
index e39ea3eb93..9c15e1341b 100644
--- a/backend/Cargo.toml
+++ b/backend/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "windmill"
-version = "1.694.0"
+version = "1.713.1"
authors.workspace = true
edition.workspace = true
@@ -87,7 +87,7 @@ members = [
exclude = ["./windmill-duckdb-ffi-internal", "./parsers/windmill-parser-wasm"]
[workspace.package]
-version = "1.694.0"
+version = "1.713.1"
authors = ["Ruben Fiszel "]
edition = "2021"
@@ -154,6 +154,7 @@ no_auth = ["windmill-api/no_auth"]
operator = ["dep:windmill-operator"]
test_job_debouncing = []
private_registry_test = []
+dev_override = ["windmill-common/dev_override"]
# Languages
python = ["windmill-worker/python", "windmill-api/python", "windmill-test-utils/python"]
rust = ["windmill-worker/rust"]
@@ -207,6 +208,36 @@ all_sqlx_features = ["all_languages", "enterprise", "enterprise_saml", "embeddin
object_store = { git = "https://github.com/apache/arrow-rs-object-store", rev = "36752c975d4f29e20b57c91f81a10872dcd48ae7" }
# Use tiberius main branch for libgssapi 0.8.1 fix (https://github.com/prisma/tiberius/issues/343)
tiberius = { git = "https://github.com/prisma/tiberius", rev = "59db57960a14b422fb3a1309aa4aa47880896ff8" }
+# Pin tokio-postgres / postgres-types / postgres-protocol to the
+# MaterializeInc fork. windmill-trigger-postgres already pulled this
+# fork in transitively for the postgres-replication crate
+# (CopyBothDuplex, LogicalReplicationStream, TupleData with binary
+# tuple support) which upstream rust-postgres has declined to merge
+# since 2021 (PR #752 → #778, both still unmerged).
+#
+# MI also carries a mitigation for the
+# Client::query_typed_raw / Client::prepare deadlock on result columns
+# whose Oid the client doesn't know about yet (citext, custom enums /
+# domains, postgis): MI's 2025-12-11 PR #33 resized the per-request
+# response channel from mpsc::channel(1) → mpsc::channel(1024).
+# bounded(1024) is sufficient for any realistic typeinfo deferral
+# (need ~2-3 batches) but leaves a theoretical failure mode at
+# >~64 MB results with a custom-Oid column. The strict-correct fix is
+# mpsc::unbounded(); a follow-up PR to MI is open proposing that.
+#
+# The [patch.crates-io] entries below force windmill-worker's
+# pg_executor (which imports `tokio_postgres::` directly from
+# crates.io) onto the same fork as windmill-trigger-postgres, so the
+# deadlock mitigation reaches both consumers.
+#
+# Upstream deadlock PRs (open, not on the critical path now that MI
+# is mitigated):
+# https://github.com/rust-postgres/rust-postgres/pull/1348
+# https://github.com/rust-postgres/rust-postgres/pull/1349
+# Reproducer: https://github.com/rubenfiszel/tokio-postgres-deadlock-repro
+tokio-postgres = { git = "https://github.com/MaterializeInc/rust-postgres", rev = "78c1222577bb091d69bc22b1bc7ad01c14675abe" }
+postgres-types = { git = "https://github.com/MaterializeInc/rust-postgres", rev = "78c1222577bb091d69bc22b1bc7ad01c14675abe" }
+postgres-protocol = { git = "https://github.com/MaterializeInc/rust-postgres", rev = "78c1222577bb091d69bc22b1bc7ad01c14675abe" }
[dependencies]
anyhow.workspace = true
@@ -387,8 +418,7 @@ tokio-stream = { version = "0.1.17" }
tower = "^0"
tower-http = { version = "^0.6", features = ["trace", "cors", "catch-panic"] }
tower-cookies = "^0.11"
-#stuck because of swc for now
-serde = "=1.0.220"
+serde = "^1"
serde_json = { version = "^1", features = ["preserve_order", "raw_value"] }
serde_yml = "0.0.12"
uuid = { version = "^1", features = ["serde", "v4", "js"] }
@@ -443,21 +473,29 @@ aws-sdk-rds = "^1"
async-trait = "0.1.88"
-v8 = "=130.0.7" # Exact version NOTE: Do not forget to update version and hash in flake.nix
-deno_fetch = "0.214.0"
-deno_tls = "0.177.0"
-deno_console = "0.190.0"
-deno_url = "0.190.0"
-deno_webidl = "0.190.0"
-deno_web = "0.221.0"
-deno_io = "0.100.0"
-deno_net = "0.182.0"
-deno_core = "0.336.0"
-deno_ast = { version = "=0.44.0", features = ["transpiling"] }
-deno_permissions = "0.49.0"
-deno_runtime = { version = "0.198.0", features = ["transpile"] }
-deno_telemetry = "0.12.0"
-deno_error = "=0.5.5"
+v8 = "=137.1.0" # Exact version NOTE: Do not forget to update version and hash in flake.nix
+# deno_* pin set: deno v2.4.0 base, with deno_ast force-overridden to =0.51.0.
+# Rationale: deno_ast 0.51.0 is the first version pulling swc_common =14.0.4,
+# the first swc_common patch that dropped `pub use serde::__private as serde;`
+# (the line that capped our workspace serde pin at =1.0.220). v2.4.0's other
+# pins keep deno_tls at 0.196.0 which uses permissive `rustls ^0.23.11`,
+# compatible with aws-sdk-bedrockruntime's `^0.23.31` requirement. deno_tls
+# 0.198+ tightened that to exact `=0.23.28`, which would have made any
+# meaningful deno bump resolver-impossible against aws-sdk.
+deno_fetch = "0.233.0"
+deno_tls = "0.196.0"
+deno_console = "0.209.0"
+deno_url = "0.209.0"
+deno_webidl = "0.209.0"
+deno_web = "0.240.0"
+deno_io = "0.119.0"
+deno_fs = "0.119.0"
+deno_net = "0.201.0"
+deno_core = "0.352.0"
+deno_ast = { version = "=0.51.0", features = ["transpiling"] }
+deno_permissions = "0.68.0"
+deno_telemetry = "0.31.0"
+deno_error = "=0.6.1"
rustls-pemfile = "2.2.0"
# only used with special deno_core_mac feature to prevent ffi issue on macos, requires libffi to be installed
@@ -470,10 +508,10 @@ google-cloud-googleapis = {version = "0.16.1", features = ["pubsub"]}
winapi = { version = "0.3.9", features = ["sysinfoapi"] }
sysinfo = { version = "0.32.1" }
-swc_common = "=0.37.5"
-swc_ecma_parser = "=0.149.1"
-swc_ecma_ast = "=0.118.2"
-swc_ecma_visit = "=0.104.8"
+swc_common = "=14.0.4"
+swc_ecma_parser = "=24.0.3"
+swc_ecma_ast = "=15.0.0"
+swc_ecma_visit = "=15.0.0"
async-recursion = "^1"
@@ -517,8 +555,8 @@ wasm-bindgen-test = "^0"
convert_case = "0.6.0"
getrandom = "0.2"
tokio-postgres = {version = "^0.7", features = ["array-impls", "with-serde_json-1", "with-chrono-0_4", "with-uuid-1", "with-bit-vec-0_6"]}
-rust-postgres = { package = "tokio-postgres", git = "https://github.com/imor/rust-postgres", rev = "20265ef38e32a06f76b6f9b678e2077fc2211f6b"}
-rust-postgres-native-tls = { package = "postgres-native-tls", git = "https://github.com/imor/rust-postgres", features = ["runtime"], rev = "20265ef38e32a06f76b6f9b678e2077fc2211f6b" }
+rust-postgres = { package = "tokio-postgres", git = "https://github.com/MaterializeInc/rust-postgres", rev = "78c1222577bb091d69bc22b1bc7ad01c14675abe"}
+rust-postgres-native-tls = { package = "postgres-native-tls", git = "https://github.com/MaterializeInc/rust-postgres", features = ["runtime"], rev = "78c1222577bb091d69bc22b1bc7ad01c14675abe" }
bit-vec = "=0.6.3"
mappable-rc = "^0"
mysql_async = { version = "*", default-features = false, features = ["minimal", "default", "native-tls-tls", "rust_decimal"]}
@@ -546,7 +584,7 @@ tokio-native-tls = "^0"
openssl = "=0.10"
mail-parser = "^0"
matchit = "=0.7.3"
-rdkafka = { version = "0.36.2", features = ["cmake-build", "ssl-vendored"] }
+rdkafka = { version = "0.36.2", features = ["cmake-build", "ssl-vendored", "curl-static"] }
rdkafka-sys = "=4.9.0"
pg_escape = "0.1.1"
async-nats = "0.38.0"
diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt
index f48c738f28..4e1f516570 100644
--- a/backend/ee-repo-ref.txt
+++ b/backend/ee-repo-ref.txt
@@ -1 +1 @@
-967f961f0a88b027d894aebd03977181129477a8
+3742e0659c5e97aab03b9efeea14cd94a3ac658a
diff --git a/backend/migrations/20260505125724_list_ws_specific_versions_function.down.sql b/backend/migrations/20260505125724_list_ws_specific_versions_function.down.sql
new file mode 100644
index 0000000000..894ad8e409
--- /dev/null
+++ b/backend/migrations/20260505125724_list_ws_specific_versions_function.down.sql
@@ -0,0 +1,2 @@
+DROP FUNCTION IF EXISTS list_ws_specific_versions(TEXT, TEXT, TEXT, TEXT);
+DROP INDEX IF EXISTS workspace_settings_deploy_to_idx;
diff --git a/backend/migrations/20260505125724_list_ws_specific_versions_function.up.sql b/backend/migrations/20260505125724_list_ws_specific_versions_function.up.sql
new file mode 100644
index 0000000000..84e7d5e345
--- /dev/null
+++ b/backend/migrations/20260505125724_list_ws_specific_versions_function.up.sql
@@ -0,0 +1,166 @@
+-- Index supporting the recursive CTE in list_ws_specific_versions: each
+-- iteration probes `WHERE ws.deploy_to = r.ws_id`, which without an index
+-- on workspace_settings.deploy_to seq-scans the whole table per iteration
+-- (up to depth-cap × |workspace_settings| row reads per call). deploy_to
+-- is sparse — most workspaces don't deploy anywhere — so a partial index
+-- keeps the index small while still covering every probe.
+CREATE INDEX IF NOT EXISTS workspace_settings_deploy_to_idx
+ ON workspace_settings (deploy_to)
+ WHERE deploy_to IS NOT NULL;
+
+-- Returns the list of workspace ids related to `seed_workspace` via the
+-- `workspace_settings.deploy_to` graph (in either direction) for which a
+-- `resource` or `variable` row at `item_path` exists AND is visible to
+-- `user_email` under that workspace's RLS context.
+--
+-- For each related workspace this function:
+-- 1. resolves the user's per-workspace identity (`usr` row by email,
+-- groups via `usr_to_group` + `email_to_igroup`, folders via
+-- `folder.extra_perms`),
+-- 2. switches the role + session.* settings via `set_session_context`
+-- so the next query is evaluated under that workspace's RLS,
+-- 3. runs an `EXISTS` against the requested item table.
+--
+-- This consolidates the "fan out per-workspace, set RLS, EXISTS" loop
+-- that previously lived in the Rust handler into a single round trip.
+CREATE OR REPLACE FUNCTION list_ws_specific_versions(
+ seed_workspace TEXT,
+ user_email TEXT,
+ item_kind TEXT,
+ item_path TEXT
+) RETURNS TABLE(ws VARCHAR) AS $$
+DECLARE
+ rel RECORD;
+ usr_row RECORD;
+ user_perms TEXT[];
+ groups_csv TEXT;
+ pgroups_csv TEXT;
+ folders_read_csv TEXT;
+ folders_write_csv TEXT;
+ item_exists BOOLEAN;
+ is_super BOOLEAN;
+BEGIN
+ IF item_kind NOT IN ('resource', 'variable') THEN
+ RAISE EXCEPTION 'Invalid kind: %', item_kind;
+ END IF;
+
+ SELECT COALESCE(super_admin, false) INTO is_super
+ FROM password WHERE email = user_email;
+ is_super := COALESCE(is_super, false);
+
+ BEGIN
+ FOR rel IN
+ WITH RECURSIVE related_workspaces(ws_id, depth) AS (
+ SELECT seed_workspace::VARCHAR, 0
+ UNION
+ SELECT CASE
+ WHEN ws.workspace_id = r.ws_id THEN ws.deploy_to
+ ELSE ws.workspace_id
+ END, r.depth + 1
+ FROM workspace_settings ws, related_workspaces r
+ WHERE r.depth < 32
+ AND ((ws.workspace_id = r.ws_id AND ws.deploy_to IS NOT NULL)
+ OR ws.deploy_to = r.ws_id)
+ )
+ SELECT DISTINCT r.ws_id
+ FROM related_workspaces r
+ INNER JOIN workspace w ON w.id = r.ws_id AND w.deleted = false
+ LOOP
+ SELECT u.username, u.is_admin
+ INTO usr_row
+ FROM usr u
+ WHERE u.email = user_email
+ AND u.workspace_id = rel.ws_id
+ AND u.disabled = false;
+
+ IF NOT FOUND AND NOT is_super THEN
+ CONTINUE;
+ END IF;
+
+ IF NOT FOUND THEN
+ -- super admin without a usr row in this workspace: synthesize an
+ -- admin identity so RLS is bypassed (windmill_admin role).
+ usr_row.username := user_email;
+ usr_row.is_admin := true;
+ groups_csv := '';
+ pgroups_csv := '';
+ folders_read_csv := '';
+ folders_write_csv := '';
+ ELSE
+ SELECT
+ COALESCE(string_agg(g, ','), ''),
+ COALESCE(string_agg('g/' || g, ','), '')
+ INTO groups_csv, pgroups_csv
+ FROM (
+ SELECT group_ AS g FROM usr_to_group
+ WHERE usr_to_group.usr = usr_row.username
+ AND usr_to_group.workspace_id = rel.ws_id
+ UNION ALL
+ SELECT igroup FROM email_to_igroup WHERE email = user_email
+ ) gs;
+
+ user_perms := ARRAY['u/' || usr_row.username] || ARRAY(
+ SELECT 'g/' || g FROM (
+ SELECT group_ AS g FROM usr_to_group
+ WHERE usr = usr_row.username AND workspace_id = rel.ws_id
+ UNION ALL
+ SELECT igroup FROM email_to_igroup WHERE email = user_email
+ ) gs2
+ );
+
+ -- folders_read: every folder the user can see (write implies read);
+ -- folders_write: only those granting write access.
+ WITH user_folders AS (
+ SELECT name, EXISTS (
+ SELECT 1 FROM jsonb_each_text(extra_perms) t
+ WHERE t.key = ANY(user_perms) AND t.value::boolean IS true
+ ) AS is_write
+ FROM folder
+ WHERE extra_perms ?| user_perms AND folder.workspace_id = rel.ws_id
+ )
+ SELECT
+ COALESCE(string_agg(name, ','), ''),
+ COALESCE(string_agg(name, ',') FILTER (WHERE is_write), '')
+ INTO folders_read_csv, folders_write_csv
+ FROM user_folders;
+
+ IF is_super THEN
+ usr_row.is_admin := true;
+ END IF;
+ END IF;
+
+ PERFORM set_session_context(
+ usr_row.is_admin,
+ usr_row.username,
+ groups_csv,
+ pgroups_csv,
+ folders_read_csv,
+ folders_write_csv
+ );
+
+ EXECUTE format(
+ 'SELECT EXISTS(SELECT 1 FROM %I WHERE workspace_id = $1 AND path = $2)',
+ item_kind
+ )
+ INTO item_exists
+ USING rel.ws_id, item_path;
+
+ IF item_exists THEN
+ ws := rel.ws_id;
+ RETURN NEXT;
+ END IF;
+ END LOOP;
+ EXCEPTION WHEN OTHERS THEN
+ -- Reset to a deny-default state before re-raising so a half-set
+ -- session context can't leak past the failed call.
+ PERFORM set_session_context(false, '', '', '', '', '');
+ RAISE;
+ END;
+
+ -- Reset to a deny-default state on the happy path too. SET LOCAL is
+ -- transaction-scoped so this also unwinds at transaction end, but
+ -- being explicit defends against the function being called inside a
+ -- longer outer transaction.
+ PERFORM set_session_context(false, '', '', '', '', '');
+END;
+$$ LANGUAGE plpgsql;
diff --git a/backend/migrations/20260511075225_add_assets_to_operator_settings.down.sql b/backend/migrations/20260511075225_add_assets_to_operator_settings.down.sql
new file mode 100644
index 0000000000..c38c2a1e42
--- /dev/null
+++ b/backend/migrations/20260511075225_add_assets_to_operator_settings.down.sql
@@ -0,0 +1,19 @@
+-- Remove "assets" key from operator_settings
+UPDATE workspace_settings
+SET operator_settings = operator_settings - 'assets'
+WHERE operator_settings IS NOT NULL
+ AND operator_settings ? 'assets';
+
+-- Revert the column default
+ALTER TABLE workspace_settings
+ALTER COLUMN operator_settings SET DEFAULT '{
+ "runs": true,
+ "groups": true,
+ "folders": true,
+ "workers": true,
+ "triggers": true,
+ "resources": true,
+ "schedules": true,
+ "variables": true,
+ "audit_logs": true
+}';
diff --git a/backend/migrations/20260511075225_add_assets_to_operator_settings.up.sql b/backend/migrations/20260511075225_add_assets_to_operator_settings.up.sql
new file mode 100644
index 0000000000..96aba860ec
--- /dev/null
+++ b/backend/migrations/20260511075225_add_assets_to_operator_settings.up.sql
@@ -0,0 +1,21 @@
+-- Add "assets": true to operator_settings for all workspaces that have operator_settings
+-- but don't already have an "assets" key
+UPDATE workspace_settings
+SET operator_settings = operator_settings || '{"assets": true}'::jsonb
+WHERE operator_settings IS NOT NULL
+ AND NOT operator_settings ? 'assets';
+
+-- Update the column default to include assets
+ALTER TABLE workspace_settings
+ALTER COLUMN operator_settings SET DEFAULT '{
+ "runs": true,
+ "groups": true,
+ "folders": true,
+ "workers": true,
+ "triggers": true,
+ "resources": true,
+ "schedules": true,
+ "variables": true,
+ "audit_logs": true,
+ "assets": true
+}';
diff --git a/backend/migrations/20260512200642_add_edited_at_to_variable.down.sql b/backend/migrations/20260512200642_add_edited_at_to_variable.down.sql
new file mode 100644
index 0000000000..efacb3376e
--- /dev/null
+++ b/backend/migrations/20260512200642_add_edited_at_to_variable.down.sql
@@ -0,0 +1,3 @@
+ALTER TABLE variable
+ DROP COLUMN IF EXISTS edited_by,
+ DROP COLUMN IF EXISTS edited_at;
diff --git a/backend/migrations/20260512200642_add_edited_at_to_variable.up.sql b/backend/migrations/20260512200642_add_edited_at_to_variable.up.sql
new file mode 100644
index 0000000000..46fc8ad8be
--- /dev/null
+++ b/backend/migrations/20260512200642_add_edited_at_to_variable.up.sql
@@ -0,0 +1,15 @@
+-- Add `edited_at` and `edited_by` so the UI can detect when a variable has
+-- been modified remotely while a local autosave was in flight (see the
+-- UserDraft staleness check). Mirrors what `resource` already has.
+--
+-- Backfill: existing rows get `edited_at = now()` via the column's DEFAULT.
+-- All pre-migration variables therefore appear to share a single edit
+-- timestamp (the migration time). The staleness check only consumes
+-- `edited_at` as an opaque rev string — it doesn't display or sort on it —
+-- and only after the user edits a variable forward at least once. So the
+-- collision is harmless: no UI flow looks at the pre-migration timestamp
+-- before it gets overwritten by a real edit.
+
+ALTER TABLE variable
+ ADD COLUMN edited_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
+ ADD COLUMN edited_by VARCHAR(50);
diff --git a/backend/migrations/20260513095235_token_read_only.down.sql b/backend/migrations/20260513095235_token_read_only.down.sql
new file mode 100644
index 0000000000..e5380a3b02
--- /dev/null
+++ b/backend/migrations/20260513095235_token_read_only.down.sql
@@ -0,0 +1 @@
+ALTER TABLE token DROP COLUMN IF EXISTS read_only;
diff --git a/backend/migrations/20260513095235_token_read_only.up.sql b/backend/migrations/20260513095235_token_read_only.up.sql
new file mode 100644
index 0000000000..4fdeb9db3a
--- /dev/null
+++ b/backend/migrations/20260513095235_token_read_only.up.sql
@@ -0,0 +1,4 @@
+-- Add a flag to restrict a token to read-only HTTP endpoints.
+-- Orthogonal to `scopes`: even if scopes grant write/run, this flag denies
+-- mutating methods (POST/PUT/PATCH/DELETE) and Run actions.
+ALTER TABLE token ADD COLUMN read_only BOOLEAN NOT NULL DEFAULT false;
diff --git a/backend/migrations/20260514130526_fix_auto_kind_lib_for_runnable_script_kinds.down.sql b/backend/migrations/20260514130526_fix_auto_kind_lib_for_runnable_script_kinds.down.sql
new file mode 100644
index 0000000000..bb9b57ac0b
--- /dev/null
+++ b/backend/migrations/20260514130526_fix_auto_kind_lib_for_runnable_script_kinds.down.sql
@@ -0,0 +1,3 @@
+-- No-op: clearing a stray `auto_kind = 'lib'` value on failure/trigger/approval
+-- scripts is not reversible (the original NULL/'lib' distinction is lost), and
+-- restoring `'lib'` here would re-hide these scripts from their pickers.
diff --git a/backend/migrations/20260514130526_fix_auto_kind_lib_for_runnable_script_kinds.up.sql b/backend/migrations/20260514130526_fix_auto_kind_lib_for_runnable_script_kinds.up.sql
new file mode 100644
index 0000000000..e2038bb78c
--- /dev/null
+++ b/backend/migrations/20260514130526_fix_auto_kind_lib_for_runnable_script_kinds.up.sql
@@ -0,0 +1,9 @@
+-- Failure, Trigger, and Approval scripts are runnable entrypoints by
+-- definition. A prior parser regression occasionally classified them as
+-- `auto_kind = 'lib'`, which hid them from the flow error-handler /
+-- trigger / approval pickers. Clear those stray values so existing
+-- affected scripts re-appear without requiring a redeploy.
+UPDATE script
+SET auto_kind = NULL
+WHERE auto_kind = 'lib'
+ AND kind IN ('failure', 'trigger', 'approval');
diff --git a/backend/migrations/20260514233244_convert_draft_created_at_to_timestamptz.down.sql b/backend/migrations/20260514233244_convert_draft_created_at_to_timestamptz.down.sql
new file mode 100644
index 0000000000..31a1f44859
--- /dev/null
+++ b/backend/migrations/20260514233244_convert_draft_created_at_to_timestamptz.down.sql
@@ -0,0 +1,5 @@
+-- Symmetric to the up migration: Postgres's default `TIMESTAMPTZ -> TIMESTAMP`
+-- cast strips the timezone by representing the instant in the session's
+-- current timezone, mirroring how the original `now()` values were
+-- truncated on insert.
+ALTER TABLE draft ALTER COLUMN created_at TYPE TIMESTAMP;
diff --git a/backend/migrations/20260514233244_convert_draft_created_at_to_timestamptz.up.sql b/backend/migrations/20260514233244_convert_draft_created_at_to_timestamptz.up.sql
new file mode 100644
index 0000000000..79f20611d4
--- /dev/null
+++ b/backend/migrations/20260514233244_convert_draft_created_at_to_timestamptz.up.sql
@@ -0,0 +1,12 @@
+-- `draft.created_at` was originally created as `TIMESTAMP` (no timezone). The
+-- new `*WithDraft` API responses surface it as `chrono::DateTime` for the
+-- frontend's staleness check, which requires `TIMESTAMPTZ`.
+--
+-- We rely on Postgres's default `TIMESTAMP -> TIMESTAMPTZ` cast (no explicit
+-- USING), which interprets each existing wall-clock value in the session's
+-- current timezone. That's the exact semantics under which the original
+-- `INSERT ... DEFAULT now()` values were truncated to TIMESTAMP — so the
+-- conversion is a no-op on UTC servers (the common case) and correctly
+-- recovers the original instant on non-UTC servers, instead of shifting all
+-- pre-migration timestamps by the server's tz offset.
+ALTER TABLE draft ALTER COLUMN created_at TYPE TIMESTAMPTZ;
diff --git a/backend/migrations/20260519081840_audit_logs_s3_anchor_on_enable.down.sql b/backend/migrations/20260519081840_audit_logs_s3_anchor_on_enable.down.sql
new file mode 100644
index 0000000000..a671c9bcbd
--- /dev/null
+++ b/backend/migrations/20260519081840_audit_logs_s3_anchor_on_enable.down.sql
@@ -0,0 +1,2 @@
+DROP TRIGGER IF EXISTS audit_logs_s3_anchor_trigger ON global_settings;
+DROP FUNCTION IF EXISTS audit_logs_s3_anchor_on_enable();
diff --git a/backend/migrations/20260519081840_audit_logs_s3_anchor_on_enable.up.sql b/backend/migrations/20260519081840_audit_logs_s3_anchor_on_enable.up.sql
new file mode 100644
index 0000000000..9bc86943e4
--- /dev/null
+++ b/backend/migrations/20260519081840_audit_logs_s3_anchor_on_enable.up.sql
@@ -0,0 +1,42 @@
+-- When `store_audit_logs_s3` is enabled, atomically anchor the audit→object
+-- store export cursor *in the enabling transaction*. The cursor lives in the
+-- dedicated `background_task_state` table (NOT `global_settings`, which is
+-- conceptually user-configurable instance config exposed via the settings
+-- UI/export). Capturing txid_snapshot_xmin here (rather than the settings
+-- row's own xmin, or a later async/first-tick read) is the only boundary that
+-- is correct for the common case where audit_log() runs in a caller
+-- transaction that acquired its xid before the enable: such a transaction is
+-- in-flight at this snapshot, so its xid >= the snapshot xmin and its
+-- post-enable audit rows are still exported.
+-- The `1970-01-01` last_ts is a "bootstrap, not yet exported" sentinel: the
+-- first export run uses an epoch timestamp floor (no partition pruning) so an
+-- arbitrarily old / delayed backlog is not dropped, then sets a real last_ts.
+-- ON CONFLICT DO NOTHING => idempotent, HA-safe, never overwrites (a
+-- disable/re-enable resumes from the preserved cursor). The WHEN clause limits
+-- the trigger to the `store_audit_logs_s3` row. The task name literal must
+-- match `windmill_common::global_settings::AUDIT_LOGS_S3_EXPORT_TASK`.
+
+CREATE OR REPLACE FUNCTION audit_logs_s3_anchor_on_enable()
+RETURNS TRIGGER AS $$
+BEGIN
+ IF NEW.value = to_jsonb(true)
+ AND (TG_OP = 'INSERT' OR OLD.value IS DISTINCT FROM NEW.value) THEN
+ INSERT INTO background_task_state (name, value)
+ VALUES (
+ 'audit_logs_s3_export',
+ jsonb_build_object(
+ 'last_xmin', txid_snapshot_xmin(txid_current_snapshot())::bigint,
+ 'last_ts', '1970-01-01T00:00:00+00:00'
+ )
+ )
+ ON CONFLICT (name) DO NOTHING;
+ END IF;
+ RETURN NEW;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE TRIGGER audit_logs_s3_anchor_trigger
+AFTER INSERT OR UPDATE OF value ON global_settings
+FOR EACH ROW
+WHEN (NEW.name = 'store_audit_logs_s3')
+EXECUTE FUNCTION audit_logs_s3_anchor_on_enable();
diff --git a/backend/oauth_connect.json b/backend/oauth_connect.json
index d57e700c0d..d18c8c8d24 100644
--- a/backend/oauth_connect.json
+++ b/backend/oauth_connect.json
@@ -170,5 +170,16 @@
"full_api_access"
],
"extra_params": {}
+ },
+ "docusign": {
+ "auth_url": "https://account.docusign.com/oauth/auth",
+ "token_url": "https://account.docusign.com/oauth/token",
+ "scopes": [
+ "signature"
+ ],
+ "sandbox": {
+ "auth_url": "https://account-d.docusign.com/oauth/auth",
+ "token_url": "https://account-d.docusign.com/oauth/token"
+ }
}
}
diff --git a/backend/parsers/windmill-parser-ts-asset/src/lib.rs b/backend/parsers/windmill-parser-ts-asset/src/lib.rs
index bc31a16c99..39302fb6fa 100644
--- a/backend/parsers/windmill-parser-ts-asset/src/lib.rs
+++ b/backend/parsers/windmill-parser-ts-asset/src/lib.rs
@@ -1,7 +1,7 @@
use std::collections::HashMap;
use swc_common::{sync::Lrc, FileName, SourceMap, Spanned};
-use swc_ecma_ast::{CallExpr, Expr, Lit, MemberExpr, MemberProp, Str};
+use swc_ecma_ast::{CallExpr, Expr, Lit, MemberExpr, MemberProp, ObjectLit, Prop, PropName, Str};
use swc_ecma_parser::{lexer::Lexer, Parser, StringInput, Syntax, TsSyntax};
use swc_ecma_visit::{Visit, VisitWith};
use windmill_parser::asset_parser::{
@@ -12,7 +12,7 @@ use AssetUsageAccessType::*;
pub fn parse_assets(code: &str) -> anyhow::Result {
let cm: Lrc = Default::default();
- let fm = cm.new_source_file(FileName::Custom("main.ts".into()).into(), code.into());
+ let fm = cm.new_source_file(FileName::Custom("main.ts".into()).into(), code.to_string());
let lexer = Lexer::new(
// We want to parse ecmascript
Syntax::Typescript(TsSyntax::default()),
@@ -309,6 +309,56 @@ impl Visit for AssetsFinder {
}
}
+/// Extract a string-literal property value from an object literal.
+/// Returns `Some(value)` for `{ name: "value" }`, ignoring computed,
+/// shorthand, spread, and non-string-literal properties.
+fn object_str_prop(obj: &ObjectLit, name: &str) -> Option {
+ for prop in &obj.props {
+ let swc_ecma_ast::PropOrSpread::Prop(p) = prop else {
+ continue;
+ };
+ let Prop::KeyValue(kv) = p.as_ref() else {
+ continue;
+ };
+ let key = match &kv.key {
+ PropName::Ident(i) => i.sym.as_str(),
+ PropName::Str(s) => s.value.as_str(),
+ _ => continue,
+ };
+ if key != name {
+ continue;
+ }
+ if let Expr::Lit(Lit::Str(s)) = kv.value.as_ref() {
+ return Some(s.value.to_string());
+ }
+ }
+ None
+}
+
+/// Resolve the SDK `S3Object` argument of `loadS3File`/`loadS3FileStream`/
+/// `writeS3File` to a canonical asset path, mirroring the runtime
+/// `parseS3Object`: an object `{ s3: "", storage?: "" }` maps to
+/// the URI `s3:///` (empty bucket for default storage, i.e.
+/// `s3:///`), and a bare `"s3://bucket/key"` string is passed through.
+/// The resulting URI is fed through `parse_asset_syntax` so the stored path
+/// matches the `// on s3:///…` trigger form exactly.
+fn s3_object_arg_path(arg: &Expr) -> Option {
+ let uri = match arg {
+ Expr::Lit(Lit::Str(s)) => s.value.to_string(),
+ Expr::Object(obj) => {
+ let key = object_str_prop(obj, "s3")?;
+ let storage = object_str_prop(obj, "storage").unwrap_or_default();
+ format!("s3://{storage}/{key}")
+ }
+ _ => return None,
+ };
+ Some(
+ parse_asset_syntax(&uri, false)
+ .map(|(_, p)| p.to_string())
+ .unwrap_or(uri),
+ )
+}
+
impl AssetsFinder {
fn visit_call_expr_inner(&mut self, node: &swc_ecma_ast::CallExpr) -> Result<(), ()> {
let ident = match node.callee.as_expr().map(AsRef::as_ref) {
@@ -331,20 +381,20 @@ impl AssetsFinder {
let arg_value = node.args.get(arg_pos);
- match arg_value.map(|e| e.expr.as_ref()) {
- Some(Expr::Lit(Lit::Str(Str { value, .. }))) => {
- let path = parse_asset_syntax(&value, false)
- .map(|(_, p)| p)
- .unwrap_or(&value);
- self.assets.push(ParseAssetsResult {
- kind,
- path: path.to_string(),
- access_type,
- columns: None,
- });
- }
+ // S3 helpers take an `S3Object` (`{ s3, storage? }`) or an
+ // `s3://bucket/key` string — the form every real script uses. Other
+ // helpers take a bare resource-path string literal.
+ let is_s3_helper = matches!(kind, AssetKind::S3Object);
+
+ let path = match arg_value.map(|e| e.expr.as_ref()) {
+ Some(arg) if is_s3_helper => s3_object_arg_path(arg).ok_or(())?,
+ Some(Expr::Lit(Lit::Str(Str { value, .. }))) => parse_asset_syntax(&value, false)
+ .map(|(_, p)| p.to_string())
+ .unwrap_or_else(|| value.to_string()),
_ => return Err(()),
- }
+ };
+ self.assets
+ .push(ParseAssetsResult { kind, path, access_type, columns: None });
Ok(())
}
}
@@ -375,6 +425,136 @@ mod tests {
);
}
+ #[test]
+ fn test_ts_asset_parser_write_s3_object_arg() {
+ // The SDK signature is `writeS3File(s3object: S3Object, ...)` and every
+ // real script passes the object form with a bare key. It must resolve
+ // to the same canonical path as a `// on s3:///` trigger.
+ let input = r#"
+ import * as wmill from "windmill-client"
+ export async function main() {
+ await wmill.writeS3File(
+ { s3: "pipelines/km_real/raw_events.json" },
+ JSON.stringify([]),
+ undefined,
+ "application/json"
+ )
+ }
+ "#;
+ let s = parse_assets(input);
+ assert_eq!(
+ s.map(|r| r.assets).map_err(|e| e.to_string()),
+ Ok(vec![ParseAssetsResult {
+ kind: AssetKind::S3Object,
+ path: "/pipelines/km_real/raw_events.json".to_string(),
+ access_type: Some(W),
+ columns: None,
+ },])
+ );
+ }
+
+ #[test]
+ fn test_ts_asset_parser_s3_object_with_storage() {
+ // `{ s3, storage }` maps to `s3:///`, matching the
+ // `s3://bucket/key` string form and `parseS3Object`.
+ let input = r#"
+ import * as wmill from "windmill-client"
+ export async function main() {
+ await wmill.loadS3File({ s3: "dir/in.csv", storage: "mybucket" })
+ }
+ "#;
+ let s = parse_assets(input);
+ assert_eq!(
+ s.map(|r| r.assets).map_err(|e| e.to_string()),
+ Ok(vec![ParseAssetsResult {
+ kind: AssetKind::S3Object,
+ path: "mybucket/dir/in.csv".to_string(),
+ access_type: Some(R),
+ columns: None,
+ },])
+ );
+ }
+
+ #[test]
+ fn test_ts_asset_parser_multiple_s3_object_writes() {
+ // Mirrors the f/km/r_seed shape: several direct object-form writes in
+ // main() — all four outputs must be detected.
+ let input = r#"
+ import * as wmill from "windmill-client"
+ export async function main() {
+ await wmill.writeS3File({ s3: "pipelines/km_real/raw_events.json" }, "[]")
+ await wmill.writeS3File({ s3: "pipelines/km_real/enriched.json" }, "[]")
+ await wmill.writeS3File({ s3: "pipelines/km_real/summary.json" }, "[]")
+ await wmill.writeS3File({ s3: "pipelines/km_real/report.json" }, "{}")
+ }
+ "#;
+ // merge_assets returns a deterministic (path-sorted) order.
+ let s = parse_assets(input);
+ assert_eq!(
+ s.map(|r| r.assets).map_err(|e| e.to_string()),
+ Ok(vec![
+ ParseAssetsResult {
+ kind: AssetKind::S3Object,
+ path: "/pipelines/km_real/enriched.json".to_string(),
+ access_type: Some(W),
+ columns: None,
+ },
+ ParseAssetsResult {
+ kind: AssetKind::S3Object,
+ path: "/pipelines/km_real/raw_events.json".to_string(),
+ access_type: Some(W),
+ columns: None,
+ },
+ ParseAssetsResult {
+ kind: AssetKind::S3Object,
+ path: "/pipelines/km_real/report.json".to_string(),
+ access_type: Some(W),
+ columns: None,
+ },
+ ParseAssetsResult {
+ kind: AssetKind::S3Object,
+ path: "/pipelines/km_real/summary.json".to_string(),
+ access_type: Some(W),
+ columns: None,
+ },
+ ])
+ );
+ }
+
+ #[test]
+ fn test_ts_asset_parser_s3_object_quoted_key() {
+ let input = r#"
+ import * as wmill from "windmill-client"
+ export async function main() {
+ await wmill.writeS3File({ "s3": "out.json" }, "{}")
+ }
+ "#;
+ let s = parse_assets(input);
+ assert_eq!(
+ s.map(|r| r.assets).map_err(|e| e.to_string()),
+ Ok(vec![ParseAssetsResult {
+ kind: AssetKind::S3Object,
+ path: "/out.json".to_string(),
+ access_type: Some(W),
+ columns: None,
+ },])
+ );
+ }
+
+ #[test]
+ fn test_ts_asset_parser_s3_object_dynamic_key_no_false_positive() {
+ // A computed key can't be resolved statically — must yield nothing
+ // rather than a bogus path.
+ let input = r#"
+ import * as wmill from "windmill-client"
+ export async function main(name: string) {
+ await wmill.writeS3File({ s3: `pipelines/${name}.json` }, "{}")
+ }
+ "#;
+ let s = parse_assets(input);
+ assert_eq!(s.map(|r| r.assets).map_err(|e| e.to_string()), Ok(vec![]));
+ }
+
#[test]
fn test_ts_asset_parser_unused_sql() {
let input = r#"
diff --git a/backend/parsers/windmill-parser-ts/src/lib.rs b/backend/parsers/windmill-parser-ts/src/lib.rs
index e63b0ef680..1e78ec8665 100644
--- a/backend/parsers/windmill-parser-ts/src/lib.rs
+++ b/backend/parsers/windmill-parser-ts/src/lib.rs
@@ -77,32 +77,36 @@ impl Visit for ImportsFinder {
}
fn visit_export_all(&mut self, node: &swc_ecma_ast::ExportAll) {
- if !self.skip_type_only || node.type_only {
+ if self.skip_type_only && node.type_only {
return;
}
self.process_raw(node.src.raw.as_ref().map(|x| x.to_string()));
}
fn visit_named_export(&mut self, node: &swc_ecma_ast::NamedExport) {
- if node.src.is_none() || !self.skip_type_only || node.type_only {
+ if node.src.is_none() {
return;
}
- if node.specifiers.len() > 0 {
- let mut is_type_only = true;
- for specifier in node.specifiers.iter() {
- match specifier {
- swc_ecma_ast::ExportSpecifier::Named(swc_ecma_ast::ExportNamedSpecifier {
- is_type_only,
- ..
- }) if *is_type_only => (),
- _ => {
- is_type_only = false;
- break;
+ if self.skip_type_only {
+ if node.type_only {
+ return;
+ }
+ if node.specifiers.len() > 0 {
+ let mut is_type_only = true;
+ for specifier in node.specifiers.iter() {
+ match specifier {
+ swc_ecma_ast::ExportSpecifier::Named(
+ swc_ecma_ast::ExportNamedSpecifier { is_type_only, .. },
+ ) if *is_type_only => (),
+ _ => {
+ is_type_only = false;
+ break;
+ }
}
}
- }
- if is_type_only {
- return;
+ if is_type_only {
+ return;
+ }
}
}
@@ -125,7 +129,7 @@ impl Visit for ImportsFinder {
/// See also: [`parse_relative_imports`] for resolved absolute paths.
pub fn parse_expr_for_imports(code: &str, skip_type_only: bool) -> anyhow::Result> {
let cm: Lrc = Default::default();
- let fm = cm.new_source_file(FileName::Custom("main.d.ts".into()).into(), code.into());
+ let fm = cm.new_source_file(FileName::Custom("main.d.ts".into()).into(), code.to_string());
let mut tss = TsSyntax::default();
tss.disallow_ambiguous_jsx_like;
tss.tsx = true;
@@ -259,7 +263,7 @@ impl Visit for OutputFinder {
pub fn parse_expr_for_ids(code: &str) -> anyhow::Result> {
let cm: Lrc = Default::default();
- let fm = cm.new_source_file(FileName::Custom("main.ts".into()).into(), code.into());
+ let fm = cm.new_source_file(FileName::Custom("main.ts".into()).into(), code.to_string());
let lexer = Lexer::new(
// We want to parse ecmascript
Syntax::Es(EsSyntax { jsx: false, ..Default::default() }),
@@ -301,7 +305,7 @@ pub fn parse_deno_signature(
entrypoint_override: Option,
) -> anyhow::Result {
let cm: Lrc = Default::default();
- let fm = cm.new_source_file(FileName::Custom("main.ts".into()).into(), code.into());
+ let fm = cm.new_source_file(FileName::Custom("main.ts".into()).into(), code.to_string());
let lexer = Lexer::new(
// We want to parse ecmascript
Syntax::Typescript(TsSyntax::default()),
diff --git a/backend/parsers/windmill-parser-ts/tests/tests.rs b/backend/parsers/windmill-parser-ts/tests/tests.rs
index 0243ccf06d..4309018fb4 100644
--- a/backend/parsers/windmill-parser-ts/tests/tests.rs
+++ b/backend/parsers/windmill-parser-ts/tests/tests.rs
@@ -936,4 +936,27 @@ mod tests {
let result = parse_relative_imports(code, "f/one/two/three/script").unwrap();
assert_eq!(result, vec!["f/b", "f/one/a"]);
}
+
+ #[test]
+ fn test_relative_imports_includes_re_exports() {
+ // Barrel re-exports must be captured as relative imports — without
+ // them, importers reaching helpers via a barrel file lose the edge in
+ // the dependency tree and the dep job 404s on the sibling fetches.
+ let code = r#"
+ export * from "./types.ts";
+ export { WorkflowError } from "./WorkflowError.ts";
+ export * as factory from "./errorFactory.ts";
+ export type { ErrorKind } from "./types-only.ts";
+ "#;
+ let result = parse_relative_imports(code, "f/lib/errors/index").unwrap();
+ assert_eq!(
+ result,
+ vec![
+ "f/lib/errors/WorkflowError",
+ "f/lib/errors/errorFactory",
+ "f/lib/errors/types",
+ "f/lib/errors/types-only",
+ ]
+ );
+ }
}
diff --git a/backend/parsers/windmill-parser-wac/src/typescript.rs b/backend/parsers/windmill-parser-wac/src/typescript.rs
index 87fa7cef01..9ad84a2e97 100644
--- a/backend/parsers/windmill-parser-wac/src/typescript.rs
+++ b/backend/parsers/windmill-parser-wac/src/typescript.rs
@@ -712,7 +712,7 @@ fn extract_ts_params(params: &[swc_ecma_ast::Param], cm: &Lrc) -> Vec
pub fn parse_ts_workflow(code: &str) -> Result> {
let cm: Lrc = Default::default();
- let fm = cm.new_source_file(FileName::Custom("workflow.ts".into()).into(), code.into());
+ let fm = cm.new_source_file(FileName::Custom("workflow.ts".into()).into(), code.to_string());
let lexer = Lexer::new(
Syntax::Typescript(TsSyntax::default()),
Default::default(),
diff --git a/backend/parsers/windmill-parser-wasm/Cargo.lock b/backend/parsers/windmill-parser-wasm/Cargo.lock
index 68cb9c8580..94b979af53 100644
--- a/backend/parsers/windmill-parser-wasm/Cargo.lock
+++ b/backend/parsers/windmill-parser-wasm/Cargo.lock
@@ -6183,7 +6183,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windmill-common"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"aho-corasick",
"anyhow",
@@ -6263,7 +6263,7 @@ dependencies = [
[[package]]
name = "windmill-macros"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"proc-macro2",
"quote",
@@ -6275,7 +6275,7 @@ dependencies = [
[[package]]
name = "windmill-parser"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"convert_case",
"serde",
@@ -6284,7 +6284,7 @@ dependencies = [
[[package]]
name = "windmill-parser-bash"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"lazy_static",
@@ -6296,7 +6296,7 @@ dependencies = [
[[package]]
name = "windmill-parser-csharp"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"serde_json",
@@ -6308,7 +6308,7 @@ dependencies = [
[[package]]
name = "windmill-parser-go"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"gosyn",
@@ -6320,7 +6320,7 @@ dependencies = [
[[package]]
name = "windmill-parser-graphql"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"lazy_static",
@@ -6332,7 +6332,7 @@ dependencies = [
[[package]]
name = "windmill-parser-java"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"serde_json",
@@ -6344,7 +6344,7 @@ dependencies = [
[[package]]
name = "windmill-parser-nu"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"nu-parser",
@@ -6355,7 +6355,7 @@ dependencies = [
[[package]]
name = "windmill-parser-php"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"itertools 0.14.0",
@@ -6366,7 +6366,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"itertools 0.14.0",
@@ -6378,7 +6378,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py-asset"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"rustpython-ast",
@@ -6389,7 +6389,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py-imports"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"async-recursion",
@@ -6411,7 +6411,7 @@ dependencies = [
[[package]]
name = "windmill-parser-r"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"serde_json",
@@ -6423,7 +6423,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ruby"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"lazy_static",
@@ -6437,7 +6437,7 @@ dependencies = [
[[package]]
name = "windmill-parser-rust"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"convert_case",
@@ -6454,7 +6454,7 @@ dependencies = [
[[package]]
name = "windmill-parser-sql"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"lazy_static",
@@ -6467,7 +6467,7 @@ dependencies = [
[[package]]
name = "windmill-parser-sql-asset"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"serde",
@@ -6479,7 +6479,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ts"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"lazy_static",
@@ -6497,7 +6497,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ts-asset"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"serde-wasm-bindgen",
@@ -6513,7 +6513,7 @@ dependencies = [
[[package]]
name = "windmill-parser-wac"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"rustpython-ast",
@@ -6529,7 +6529,7 @@ dependencies = [
[[package]]
name = "windmill-parser-wasm"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"getrandom 0.2.17",
@@ -6561,7 +6561,7 @@ dependencies = [
[[package]]
name = "windmill-parser-yaml"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"serde",
@@ -6572,7 +6572,7 @@ dependencies = [
[[package]]
name = "windmill-types"
-version = "1.694.0"
+version = "1.713.1"
dependencies = [
"anyhow",
"bitflags",
diff --git a/backend/parsers/windmill-parser-wasm/Cargo.toml b/backend/parsers/windmill-parser-wasm/Cargo.toml
index 86603edffd..e2bb2affad 100644
--- a/backend/parsers/windmill-parser-wasm/Cargo.toml
+++ b/backend/parsers/windmill-parser-wasm/Cargo.toml
@@ -12,7 +12,7 @@ resolver = "2"
members = ["."]
[workspace.package]
-version = "1.694.0"
+version = "1.713.1"
edition = "2021"
authors = ["Ruben Fiszel "]
diff --git a/backend/src/db_connect.rs b/backend/src/db_connect.rs
index 3860c18909..deda032058 100644
--- a/backend/src/db_connect.rs
+++ b/backend/src/db_connect.rs
@@ -6,6 +6,8 @@ use windmill_common::{
pub const DEFAULT_MAX_CONNECTIONS_SERVER: u32 = 50;
pub const DEFAULT_MAX_CONNECTIONS_WORKER: u32 = 5;
pub const DEFAULT_MAX_CONNECTIONS_INDEXER: u32 = 5;
+#[cfg(feature = "operator")]
+pub const DEFAULT_MAX_CONNECTIONS_OPERATOR: u32 = 2;
pub async fn initial_connection() -> Result, error::Error> {
let connect_options = get_database_url().await?.connect_options().await?;
@@ -16,12 +18,35 @@ pub async fn initial_connection() -> Result, error::E
.map_err(|err| Error::ConnectingToDatabase(err.to_string()))
}
+/// Connect to the database for the Kubernetes operator process.
+///
+/// Long-running operator pods need IAM RDS / Entra ID token refresh just like the server,
+/// otherwise new pool connections start failing once the initial token expires (~15 min).
+#[cfg(feature = "operator")]
+pub async fn operator_connection(
+ #[cfg(all(feature = "enterprise", feature = "private"))]
+ killpill_rx: tokio::sync::broadcast::Receiver<()>,
+) -> anyhow::Result> {
+ let database_url = get_database_url().await?;
+ let pool = connect(
+ database_url.clone(),
+ DEFAULT_MAX_CONNECTIONS_OPERATOR,
+ false,
+ )
+ .await?;
+
+ #[cfg(all(feature = "enterprise", feature = "private"))]
+ spawn_token_refresh_task(pool.clone(), database_url, killpill_rx);
+
+ Ok(pool)
+}
+
pub async fn connect_db(
server_mode: bool,
indexer_mode: bool,
worker_mode: bool,
num_workers: i32,
- #[cfg(feature = "private")] mut killpill_rx: tokio::sync::broadcast::Receiver<()>,
+ #[cfg(feature = "private")] killpill_rx: tokio::sync::broadcast::Receiver<()>,
) -> anyhow::Result> {
use anyhow::Context;
@@ -43,70 +68,72 @@ pub async fn connect_db(
let pool = connect(database_url.clone(), max_connections, worker_mode).await?;
#[cfg(all(feature = "enterprise", feature = "private"))]
- {
- let needs_token_refresh = matches!(
- database_url,
- DatabaseUrl::IamRds(_) | DatabaseUrl::EntraId(_)
- );
- let label = match &database_url {
- DatabaseUrl::IamRds(_) => "IAM RDS",
- DatabaseUrl::EntraId(_) => "Entra ID",
- DatabaseUrl::Static(_) => "",
- };
- if needs_token_refresh {
- let pool2 = pool.clone();
- let database_url2 = database_url.clone();
- tokio::spawn(async move {
- loop {
- tokio::select! {
- _ = killpill_rx.recv() => {
- break;
- }
- _ = tokio::time::sleep(std::time::Duration::from_secs(10)) => {
- if !database_url2.needs_refresh().await {
- continue;
- }
- let new_url = tokio::time::timeout(
- std::time::Duration::from_secs(10),
- get_database_url(),
- )
- .await;
- match new_url {
- Ok(Ok(new_url)) => {
- match new_url.connect_options().await {
- Ok(connect_options) => {
- pool2.set_connect_options(connect_options);
- tracing::info!("Refreshed {label} URL successfully");
- }
- Err(e) => {
- tracing::error!(
- "Error getting {label} connect options, retrying in 10s: {e}"
- );
- continue;
- }
- }
- }
- Ok(Err(e)) => {
- tracing::error!(
- "Error refreshing {label} URL, trying again in 10s: {e}"
- );
- continue;
+ spawn_token_refresh_task(pool.clone(), database_url, killpill_rx);
+
+ Ok(pool)
+}
+
+/// Spawn a background task that refreshes IAM RDS / Entra ID tokens before they expire
+/// and updates the pool's connect options so new connections use the fresh token.
+/// No-op for static (password-based) database URLs.
+#[cfg(all(feature = "enterprise", feature = "private"))]
+pub fn spawn_token_refresh_task(
+ pool: sqlx::Pool,
+ database_url: DatabaseUrl,
+ mut killpill_rx: tokio::sync::broadcast::Receiver<()>,
+) {
+ let label = match &database_url {
+ DatabaseUrl::IamRds(_) => "IAM RDS",
+ DatabaseUrl::EntraId(_) => "Entra ID",
+ DatabaseUrl::Static(_) => return,
+ };
+ tokio::spawn(async move {
+ loop {
+ tokio::select! {
+ _ = killpill_rx.recv() => {
+ break;
+ }
+ _ = tokio::time::sleep(std::time::Duration::from_secs(10)) => {
+ if !database_url.needs_refresh().await {
+ continue;
+ }
+ let new_url = tokio::time::timeout(
+ std::time::Duration::from_secs(10),
+ get_database_url(),
+ )
+ .await;
+ match new_url {
+ Ok(Ok(new_url)) => {
+ match new_url.connect_options().await {
+ Ok(connect_options) => {
+ pool.set_connect_options(connect_options);
+ tracing::info!("Refreshed {label} URL successfully");
}
Err(e) => {
tracing::error!(
- "Timeout after 10s refreshing {label} URL, trying again in 10s: {e}"
+ "Error getting {label} connect options, retrying in 10s: {e}"
);
continue;
}
}
}
+ Ok(Err(e)) => {
+ tracing::error!(
+ "Error refreshing {label} URL, trying again in 10s: {e}"
+ );
+ continue;
+ }
+ Err(e) => {
+ tracing::error!(
+ "Timeout after 10s refreshing {label} URL, trying again in 10s: {e}"
+ );
+ continue;
+ }
}
}
- });
+ }
}
- }
-
- Ok(pool)
+ });
}
pub async fn connect(
diff --git a/backend/src/ee_oss.rs b/backend/src/ee_oss.rs
index 2aefaf7431..3fb3ce2804 100644
--- a/backend/src/ee_oss.rs
+++ b/backend/src/ee_oss.rs
@@ -8,6 +8,16 @@ pub async fn set_license_key(_license_key: String, _db: Option<&windmill_common:
}
#[cfg(all(feature = "enterprise", not(feature = "private")))]
-pub async fn verify_license_key() -> () {
+pub async fn verify_license_key(_db: Option<&windmill_common::db::DB>) -> () {
// Implementation is not open source
}
+
+#[cfg(all(feature = "parquet", not(feature = "private")))]
+pub async fn export_audit_logs_to_object_store(_db: &windmill_common::db::DB) {
+ // Implementation is not open source (Windmill Enterprise Edition feature)
+}
+
+#[cfg(all(feature = "parquet", not(feature = "private")))]
+pub async fn anchor_audit_logs_s3_checkpoint_env_var(_db: &windmill_common::db::DB) {
+ // Implementation is not open source (Windmill Enterprise Edition feature)
+}
diff --git a/backend/src/main.rs b/backend/src/main.rs
index ce09c7f25f..29bea050ed 100644
--- a/backend/src/main.rs
+++ b/backend/src/main.rs
@@ -51,13 +51,17 @@ use windmill_common::{
JOB_DEFAULT_TIMEOUT_SECS_SETTING, JOB_ISOLATION_SETTING, JWT_SECRET_SETTING,
KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING, MAVEN_REPOS_SETTING, MAVEN_SETTINGS_XML_SETTING,
MONITOR_LOGS_ON_OBJECT_STORE_SETTING, NO_DEFAULT_MAVEN_SETTING,
- NPM_CONFIG_REGISTRY_SETTING, NUGET_CONFIG_SETTING, OAUTH_SETTING, OTEL_SETTING,
- OTEL_TRACING_PROXY_SETTING, PIP_INDEX_URL_SETTING, POWERSHELL_REPO_PAT_SETTING,
- POWERSHELL_REPO_URL_SETTING, PREVIEW_TAGS_OVERRIDE_SETTING, REQUEST_SIZE_LIMIT_SETTING,
+ NPM_CONFIG_REGISTRY_SETTING, NSJAIL_TMPFS_SIZE_MB_SETTING, NSJAIL_TMP_BACKING_SETTING,
+ NUGET_CONFIG_SETTING, OAUTH_SETTING, OTEL_SETTING, OTEL_TRACING_PROXY_SETTING,
+ PIP_INDEX_URL_SETTING, POWERSHELL_REPO_PAT_SETTING, POWERSHELL_REPO_URL_SETTING,
+ PREVIEW_TAGS_OVERRIDE_SETTING, REQUEST_SIZE_LIMIT_SETTING,
REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING, RESTART_COORDINATION_SETTING,
RETENTION_PERIOD_SECS_SETTING, RUBY_REPOS_SETTING, SAML_METADATA_SETTING,
- SCIM_TOKEN_SETTING, SMTP_SETTING, TEAMS_SETTING, TIMEOUT_WAIT_RESULT_SETTING,
- UV_EXCLUDE_NEWER_SETTING, UV_INDEX_STRATEGY_SETTING, WORKSPACE_REGISTRIES_SETTING,
+ SCIM_TOKEN_SETTING, SMTP_SETTING, STORE_AUDIT_LOGS_S3_SETTING, TEAMS_SETTING,
+ TIMEOUT_WAIT_RESULT_SETTING, UV_EXCLUDE_NEWER_SETTING, UV_INDEX_STRATEGY_SETTING,
+ UV_PYTHON_INSTALL_MIRROR_SETTING, WORKSPACE_FAIRNESS_DURATION_SECS_SETTING,
+ WORKSPACE_FAIRNESS_ENABLED_SETTING, WORKSPACE_FAIRNESS_MAX_PERCENT_SETTING,
+ WORKSPACE_FAIRNESS_MIN_TOTAL_SETTING, WORKSPACE_REGISTRIES_SETTING,
},
scripts::ScriptLang,
stats_oss::schedule_stats,
@@ -89,6 +93,20 @@ use tikv_jemallocator::Jemalloc;
#[global_allocator]
static GLOBAL: Jemalloc = Jemalloc;
+// Stock jemalloc only purges freed pages during alloc/free calls on app
+// threads, so a long-lived worker that goes quiet after a burst never runs the
+// purge: RSS freezes at the high-water mark and eventually OOMs under a hard
+// cgroup limit. Enabling the background thread makes the decay run on idle,
+// returning pages to the OS; the decay windows are left at jemalloc defaults
+// (dirty 10s, muzzy 0) on purpose — over a worker's months-long lifetime there
+// is no benefit to reclaiming more aggressively than that. jemalloc applies the
+// _RJEM_MALLOC_CONF env var after this symbol, so operators can still tune
+// decay or add prof:* for profiling.
+#[cfg(all(not(target_env = "msvc"), feature = "jemalloc"))]
+#[allow(non_upper_case_globals)]
+#[export_name = "_rjem_malloc_conf"]
+pub static malloc_conf: &[u8] = b"background_thread:true\0";
+
#[cfg(feature = "parquet")]
use windmill_common::global_settings::OBJECT_STORE_CONFIG_SETTING;
@@ -104,7 +122,9 @@ use crate::monitor::{
initial_load, load_disable_password_login, load_fork_workspace_tag_append_fork_suffix,
load_keep_job_dir, load_metrics_debug_enabled, load_preview_tags_override,
load_require_preexisting_user, load_tag_per_workspace_enabled,
- load_tag_per_workspace_workspaces, monitor_db, reload_app_workspaced_route_setting,
+ load_tag_per_workspace_workspaces, load_workspace_fairness_duration_secs,
+ load_workspace_fairness_enabled, load_workspace_fairness_max_percent,
+ load_workspace_fairness_min_total, monitor_db, reload_app_workspaced_route_setting,
reload_audit_log_retention_days_setting, reload_base_url_setting,
reload_bun_install_min_release_age_setting, reload_bunfig_install_scopes_setting,
reload_critical_alert_mute_ui_setting, reload_critical_alerts_on_token_expiry_setting,
@@ -112,9 +132,11 @@ use crate::monitor::{
reload_http_route_workspaced_route_setting, reload_hub_api_secret_setting,
reload_hub_base_url_setting, reload_instance_events_webhook_setting,
reload_job_default_timeout_setting, reload_job_isolation_setting, reload_jwt_secret_setting,
- reload_license_key, reload_npm_config_registry_setting, reload_otel_tracing_proxy_setting,
+ reload_license_key, reload_npm_config_registry_setting, reload_nsjail_tmp_backing_setting,
+ reload_nsjail_tmpfs_size_setting, reload_otel_tracing_proxy_setting,
reload_pip_index_url_setting, reload_retention_period_setting, reload_scim_token_setting,
- reload_smtp_config, reload_uv_exclude_newer_setting, reload_uv_index_strategy_setting,
+ reload_smtp_config, reload_store_audit_logs_s3_setting, reload_uv_exclude_newer_setting,
+ reload_uv_index_strategy_setting, reload_uv_python_install_mirror_setting,
reload_worker_config, MonitorIteration,
};
@@ -236,6 +258,15 @@ pub fn main() -> anyhow::Result<()> {
}
async fn cache_hub_scripts(file_path: Option) -> anyhow::Result<()> {
+ // The `cache` CLI mode never connects to the DB, so HUB_BASE_URL keeps its
+ // compiled default. Allow overriding it via env so the prebuild cache step can
+ // be pointed at a private/staging hub (e.g. a local proxy for testing).
+ if let Ok(hub_base_url) = std::env::var("HUB_BASE_URL") {
+ if !hub_base_url.is_empty() {
+ tracing::info!("Overriding hub base url from env: {hub_base_url}");
+ windmill_common::HUB_BASE_URL.store(std::sync::Arc::new(hub_base_url));
+ }
+ }
let file_path = file_path.unwrap_or("./hubPaths.json".to_string());
let mut file = File::open(&file_path)
.await
@@ -288,42 +319,56 @@ async fn cache_hub_scripts(file_path: Option) -> anyhow::Result<()> {
let job_dir = format!("{}/cache_init/{}", *WINDMILL_DIR, job_id);
create_dir_all(&job_dir)?;
if let Some(lock) = res.lockfile {
- let _ = windmill_worker::prepare_job_dir(&lock, &job_dir).await?;
- let envs = windmill_worker::get_common_bun_proc_envs(None).await;
- let _ = windmill_worker::install_bun_lockfile(
- &mut 0,
- &mut None,
- &job_id,
- "admins",
- None,
- &job_dir,
- "cache_init",
- envs.clone(),
- false,
- &mut None,
- false,
- )
- .await?;
-
- let _ = windmill_common::worker::write_file(&job_dir, "main.js", &res.content)?;
-
- if let Err(e) = windmill_worker::prebundle_bun_script(
- &res.content,
- &lock,
- &path,
- &job_id,
- "admins",
- None,
- &job_dir,
- "",
- "cache_init",
- "",
- &mut None,
- &None,
- )
- .await
- {
- panic!("Error prebundling bun script: {e:#}");
+ // The hub occasionally returns a malformed `lockfile` field — e.g. the
+ // raw script source instead of the expected `\n//bun.lockb\n`
+ // shape. A valid lockfile always starts with the package.json (`{...}`),
+ // so anything else is bogus and would make `bun install` choke trying to
+ // parse TypeScript as JSON. Skip those rather than aborting the entire cache.
+ if !lock.trim_start().starts_with('{') {
+ tracing::warn!(
+ "Hub script {path} returned a malformed lockfile (does not start with a package.json object), skipping prebundling"
+ );
+ } else {
+ let _ = windmill_worker::prepare_job_dir(&lock, &job_dir).await?;
+ let envs = windmill_worker::get_common_bun_proc_envs(None).await;
+ if let Err(e) = windmill_worker::install_bun_lockfile(
+ &mut 0,
+ &mut None,
+ &job_id,
+ "admins",
+ None,
+ &job_dir,
+ "cache_init",
+ envs.clone(),
+ false,
+ &mut None,
+ false,
+ )
+ .await
+ {
+ // A single broken hub script (malformed lockfile, missing dep, …)
+ // shouldn't abort the entire cache run — log and move on.
+ tracing::error!(
+ "Failed to install lockfile for hub script {path}, skipping: {e:#}"
+ );
+ } else if let Err(e) = windmill_worker::prebundle_bun_script(
+ &res.content,
+ &lock,
+ &path,
+ &job_id,
+ "admins",
+ None,
+ &job_dir,
+ "",
+ "cache_init",
+ "",
+ &mut None,
+ &None,
+ )
+ .await
+ {
+ tracing::error!("Failed to prebundle hub script {path}, skipping: {e:#}");
+ }
}
} else {
tracing::warn!("No lockfile found for bun script {path}, skipping...");
@@ -531,6 +576,7 @@ fn print_help() {
println!(" RUN_UPDATE_CA_CERTIFICATE_AT_START = false Run system CA update at startup");
println!(" RUN_UPDATE_CA_CERTIFICATE_PATH = /usr/sbin/update-ca-certificates Path to CA update tool");
println!(" SYNC_CACHED_RT = false Sync cached resource types to admins workspace on server start");
+ println!(" HUB_BASE_URL = https://hub.windmill.dev Hub to fetch scripts from in `cache` mode (server/worker use the DB setting instead)");
println!();
println!("Notes:");
println!("- Advanced and less commonly used settings are managed via the database and are omitted here.");
@@ -600,6 +646,7 @@ async fn windmill_main() -> anyhow::Result<()> {
return Ok(());
}
"cache" => {
+ tracing_subscriber::fmt::init();
#[cfg(feature = "embedding")]
{
println!("Caching embedding model...");
@@ -655,7 +702,24 @@ async fn windmill_main() -> anyhow::Result<()> {
tracing_subscriber::fmt::init();
tracing::info!("Starting Windmill Kubernetes operator...");
tracing::info!("Connecting to database...");
- let db = crate::db_connect::initial_connection().await?;
+
+ #[cfg(all(feature = "enterprise", feature = "private"))]
+ let (operator_killpill_tx, operator_killpill_rx) =
+ tokio::sync::broadcast::channel::<()>(2);
+
+ let db = crate::db_connect::operator_connection(
+ #[cfg(all(feature = "enterprise", feature = "private"))]
+ operator_killpill_rx,
+ )
+ .await?;
+
+ #[cfg(all(feature = "enterprise", feature = "private"))]
+ tokio::spawn(async move {
+ if let Ok(()) = tokio::signal::ctrl_c().await {
+ let _ = operator_killpill_tx.send(());
+ }
+ });
+
tracing::info!("Database connected. Starting ConfigMap watcher...");
windmill_operator::run(db).await?;
return Ok(());
@@ -1426,7 +1490,7 @@ Windmill Community Edition {GIT_VERSION}
tracing::error!("Failed to reload license key on agent: {e:#}");
}
#[cfg(feature = "enterprise")]
- ee_oss::verify_license_key().await;
+ ee_oss::verify_license_key(conn.as_sql()).await;
}
// update min version explicitly.
@@ -1715,6 +1779,26 @@ async fn process_notify_event(
tracing::error!("Error loading preview tags override: {e:#}");
}
}
+ WORKSPACE_FAIRNESS_ENABLED_SETTING => {
+ if let Err(e) = load_workspace_fairness_enabled(db).await {
+ tracing::error!("Error loading workspace fairness enabled: {e:#}");
+ }
+ }
+ WORKSPACE_FAIRNESS_MAX_PERCENT_SETTING => {
+ if let Err(e) = load_workspace_fairness_max_percent(db).await {
+ tracing::error!("Error loading workspace fairness max percent: {e:#}");
+ }
+ }
+ WORKSPACE_FAIRNESS_DURATION_SECS_SETTING => {
+ if let Err(e) = load_workspace_fairness_duration_secs(db).await {
+ tracing::error!("Error loading workspace fairness duration secs: {e:#}");
+ }
+ }
+ WORKSPACE_FAIRNESS_MIN_TOTAL_SETTING => {
+ if let Err(e) = load_workspace_fairness_min_total(db).await {
+ tracing::error!("Error loading workspace fairness min total: {e:#}");
+ }
+ }
SMTP_SETTING => {
reload_smtp_config(db).await;
}
@@ -1732,8 +1816,11 @@ async fn process_notify_event(
MONITOR_LOGS_ON_OBJECT_STORE_SETTING => {
reload_delete_logs_periodically_setting(conn).await
}
+ STORE_AUDIT_LOGS_S3_SETTING => reload_store_audit_logs_s3_setting(conn).await,
JOB_DEFAULT_TIMEOUT_SECS_SETTING => reload_job_default_timeout_setting(conn).await,
JOB_ISOLATION_SETTING => reload_job_isolation_setting(conn).await,
+ NSJAIL_TMPFS_SIZE_MB_SETTING => reload_nsjail_tmpfs_size_setting(conn).await,
+ NSJAIL_TMP_BACKING_SETTING => reload_nsjail_tmp_backing_setting(conn).await,
#[cfg(feature = "parquet")]
OBJECT_STORE_CONFIG_SETTING => {
if !disable_s3_store {
@@ -1745,6 +1832,9 @@ async fn process_notify_event(
PIP_INDEX_URL_SETTING => reload_pip_index_url_setting(conn).await,
UV_INDEX_STRATEGY_SETTING => reload_uv_index_strategy_setting(conn).await,
UV_EXCLUDE_NEWER_SETTING => reload_uv_exclude_newer_setting(conn).await,
+ UV_PYTHON_INSTALL_MIRROR_SETTING => {
+ reload_uv_python_install_mirror_setting(conn).await
+ }
BUN_INSTALL_MIN_RELEASE_AGE_SETTING => {
reload_bun_install_min_release_age_setting(conn).await
}
diff --git a/backend/src/monitor.rs b/backend/src/monitor.rs
index ef3fb99696..13f52037e7 100644
--- a/backend/src/monitor.rs
+++ b/backend/src/monitor.rs
@@ -62,11 +62,15 @@ use windmill_common::{
HUB_BASE_URL_SETTING, INSTANCE_PYTHON_VERSION_SETTING, JOB_DEFAULT_TIMEOUT_SECS_SETTING,
JOB_ISOLATION_SETTING, JWT_SECRET_SETTING, KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING,
MONITOR_LOGS_ON_OBJECT_STORE_SETTING, NPMRC_SETTING, NPM_CONFIG_REGISTRY_SETTING,
- NUGET_CONFIG_SETTING, OTEL_SETTING, OTEL_TRACING_PROXY_SETTING, PIP_INDEX_URL_SETTING,
+ NSJAIL_TMPFS_SIZE_MB_SETTING, NSJAIL_TMP_BACKING_SETTING, NUGET_CONFIG_SETTING,
+ OTEL_SETTING, OTEL_TRACING_PROXY_SETTING, PIP_INDEX_URL_SETTING,
POWERSHELL_REPO_PAT_SETTING, POWERSHELL_REPO_URL_SETTING, PREVIEW_TAGS_OVERRIDE_SETTING,
REQUEST_SIZE_LIMIT_SETTING, REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING,
RETENTION_PERIOD_SECS_SETTING, SAML_METADATA_SETTING, SCIM_TOKEN_SETTING,
- TIMEOUT_WAIT_RESULT_SETTING, UV_EXCLUDE_NEWER_SETTING, UV_INDEX_STRATEGY_SETTING,
+ STORE_AUDIT_LOGS_S3_SETTING, TIMEOUT_WAIT_RESULT_SETTING, UV_EXCLUDE_NEWER_SETTING,
+ UV_INDEX_STRATEGY_SETTING, UV_PYTHON_INSTALL_MIRROR_SETTING,
+ WORKSPACE_FAIRNESS_DURATION_SECS_SETTING, WORKSPACE_FAIRNESS_ENABLED_SETTING,
+ WORKSPACE_FAIRNESS_MAX_PERCENT_SETTING, WORKSPACE_FAIRNESS_MIN_TOTAL_SETTING,
},
indexer::load_indexer_config,
jwt::JWT_SECRET,
@@ -82,13 +86,14 @@ use windmill_common::{
store_suspended_pull_query, Connection, WorkerConfig, DEFAULT_TAGS_PER_WORKSPACE,
DEFAULT_TAGS_WORKSPACES, FORK_WORKSPACE_TAG_APPEND_FORK_SUFFIX, INDEXER_CONFIG,
PREVIEW_TAGS_OVERRIDE, SCRIPT_TOKEN_EXPIRY, SMTP_CONFIG, WINDMILL_DIR, WORKER_CONFIG,
- WORKER_GROUP,
+ WORKER_GROUP, WORKSPACE_FAIRNESS_DURATION_SECS, WORKSPACE_FAIRNESS_ENABLED,
+ WORKSPACE_FAIRNESS_MAX_PERCENT, WORKSPACE_FAIRNESS_MIN_TOTAL,
},
KillpillSender, AUDIT_LOG_RETENTION_DAYS, BASE_URL, CRITICAL_ALERTS_ON_DB_OVERSIZE,
CRITICAL_ALERTS_ON_TOKEN_EXPIRY, CRITICAL_ALERT_MUTE_UI_ENABLED, CRITICAL_ERROR_CHANNELS, DB,
DEFAULT_HUB_BASE_URL, HUB_BASE_URL, JOB_RETENTION_SECS, METRICS_DEBUG_ENABLED, METRICS_ENABLED,
MONITOR_LOGS_ON_OBJECT_STORE, OTEL_LOGS_ENABLED, OTEL_METRICS_ENABLED, OTEL_TRACING_ENABLED,
- SERVICE_LOG_RETENTION_SECS,
+ SERVICE_LOG_RETENTION_SECS, STORE_AUDIT_LOGS_S3,
};
use windmill_common::{
client::AuthedClient,
@@ -105,9 +110,10 @@ use windmill_worker::{
OtelTracingProxySettings, SameWorkerSender, WorkspaceRegistryMap, BUNFIG_INSTALL_SCOPES,
BUN_INSTALL_MIN_RELEASE_AGE, CARGO_REGISTRIES, INSTANCE_PYTHON_VERSION, JAVA_HOME_DIR,
JOB_DEFAULT_TIMEOUT, JOB_ISOLATION, KEEP_JOB_DIR, MAVEN_REPOS, MAVEN_SETTINGS_XML,
- NO_DEFAULT_MAVEN, NPMRC, NPM_CONFIG_REGISTRY, NSJAIL_AVAILABLE, NUGET_CONFIG,
- OTEL_TRACING_PROXY_SETTINGS, PIP_EXTRA_INDEX_URL, PIP_INDEX_URL, POWERSHELL_REPO_PAT,
- POWERSHELL_REPO_URL, UNSHARE_PATH, UV_EXCLUDE_NEWER, UV_INDEX_STRATEGY, WORKSPACE_REGISTRIES,
+ NO_DEFAULT_MAVEN, NPMRC, NPM_CONFIG_REGISTRY, NSJAIL_AVAILABLE, NSJAIL_TMPFS_SIZE_MB,
+ NSJAIL_TMP_BACKING, NUGET_CONFIG, OTEL_TRACING_PROXY_SETTINGS, PIP_EXTRA_INDEX_URL,
+ PIP_INDEX_URL, POWERSHELL_REPO_PAT, POWERSHELL_REPO_URL, UNSHARE_PATH, UV_EXCLUDE_NEWER,
+ UV_INDEX_STRATEGY, UV_PYTHON_INSTALL_MIRROR, WORKSPACE_REGISTRIES,
};
#[cfg(feature = "parquet")]
@@ -245,6 +251,22 @@ pub async fn initial_load(
if let Err(e) = load_preview_tags_override(db).await {
tracing::error!("Error loading preview tags override: {e:#}");
}
+
+ // Workspace fairness (cloud-only). Load the percentage/duration/min knobs
+ // *before* the enabled flag so that `load_workspace_fairness_enabled` reads
+ // current values when re-storing the pull queries.
+ if let Err(e) = load_workspace_fairness_max_percent(db).await {
+ tracing::error!("Error loading workspace fairness max percent: {e:#}");
+ }
+ if let Err(e) = load_workspace_fairness_duration_secs(db).await {
+ tracing::error!("Error loading workspace fairness duration secs: {e:#}");
+ }
+ if let Err(e) = load_workspace_fairness_min_total(db).await {
+ tracing::error!("Error loading workspace fairness min total: {e:#}");
+ }
+ if let Err(e) = load_workspace_fairness_enabled(db).await {
+ tracing::error!("Error loading workspace fairness enabled: {e:#}");
+ }
}
if server_mode {
@@ -352,6 +374,24 @@ pub async fn initial_load(
if server_mode {
reload_retention_period_setting(&conn).await;
reload_audit_log_retention_days_setting(&conn).await;
+ reload_store_audit_logs_s3_setting(&conn).await;
+ // Env-var enable has no settings-row xmin and no runtime enable event;
+ // anchor the export cursor at startup so rows committed before the
+ // first export tick are not skipped (no-op when a settings row exists
+ // or a checkpoint is already present). Audit-log S3 export is an
+ // Enterprise feature; the core logic lives in `crate::ee` (OSS gets a
+ // no-op), gated here on a valid Enterprise license.
+ #[cfg(feature = "parquet")]
+ if STORE_AUDIT_LOGS_S3.load(std::sync::atomic::Ordering::Relaxed)
+ && matches!(
+ windmill_common::ee_oss::get_license_plan().await,
+ windmill_common::ee_oss::LicensePlan::Enterprise
+ )
+ {
+ if let Some(db) = conn.as_sql() {
+ crate::ee_oss::anchor_audit_logs_s3_checkpoint_env_var(&db).await;
+ }
+ }
reload_request_size(&conn).await;
reload_saml_metadata_setting(&conn).await;
reload_scim_token_setting(&conn).await;
@@ -365,10 +405,13 @@ pub async fn initial_load(
if worker_mode {
reload_job_default_timeout_setting(&conn).await;
reload_job_isolation_setting(&conn).await;
+ reload_nsjail_tmpfs_size_setting(&conn).await;
+ reload_nsjail_tmp_backing_setting(&conn).await;
reload_extra_pip_index_url_setting(&conn).await;
reload_pip_index_url_setting(&conn).await;
reload_uv_index_strategy_setting(&conn).await;
reload_uv_exclude_newer_setting(&conn).await;
+ reload_uv_python_install_mirror_setting(&conn).await;
reload_bun_install_min_release_age_setting(&conn).await;
reload_npm_config_registry_setting(&conn).await;
reload_bunfig_install_scopes_setting(&conn).await;
@@ -519,6 +562,112 @@ pub async fn load_preview_tags_override(db: &DB) -> error::Result<()> {
Ok(())
}
+// Upper bound on the duration window. Postgres `make_interval(secs => $1::int4)` is the consumer
+// downstream, so this stays comfortably below `i32::MAX` and the subsequent `u32 -> i32` cast in
+// `workspace_fairness::refresh_overloaded` cannot wrap into a negative interval (which would
+// silently turn `now() - interval` into a future timestamp and disable the completed-jobs half
+// of the activity signal). A day is the practical ceiling for a "rolling window" knob.
+const WORKSPACE_FAIRNESS_DURATION_SECS_MAX: u64 = 86_400;
+
+/// Min-total floor is a counting threshold; cap at `u32::MAX` to make wraparound impossible
+/// while still leaving more headroom than any realistic cluster will need.
+const WORKSPACE_FAIRNESS_MIN_TOTAL_MAX: u64 = u32::MAX as u64;
+
+// Defaults used when a fairness knob is unset (row missing or row deleted via NULL/empty value).
+// Must stay in sync with the `AtomicU32::new(...)` initialisers in `windmill-common/src/worker.rs`
+// so a process that has never seen the setting reads the same value as one that just saw it
+// cleared.
+const WORKSPACE_FAIRNESS_MAX_PERCENT_DEFAULT: u32 = 50;
+const WORKSPACE_FAIRNESS_DURATION_SECS_DEFAULT: u32 = 10;
+const WORKSPACE_FAIRNESS_MIN_TOTAL_DEFAULT: u32 = 4;
+
+pub async fn load_workspace_fairness_enabled(db: &DB) -> error::Result<()> {
+ // Match the convention used by `load_preview_tags_override` /
+ // `load_fork_workspace_tag_append_fork_suffix`: on transient DB errors, leave the in-memory
+ // atomic untouched rather than silently toggling the feature off across the whole cluster
+ // (which would also trigger an unnecessary `store_pull_query` rebuild — exactly when DB load
+ // is probably highest).
+ let new_enabled =
+ match load_value_from_global_settings(db, WORKSPACE_FAIRNESS_ENABLED_SETTING).await? {
+ Some(serde_json::Value::Bool(t)) => t,
+ // Setting unset / non-bool → explicit off.
+ _ => false,
+ };
+ let prev = WORKSPACE_FAIRNESS_ENABLED.swap(new_enabled, Ordering::Relaxed);
+ // Re-store the pull queries so the fairness variants appear/disappear in
+ // lockstep with the toggle.
+ if prev != new_enabled {
+ let wc = windmill_common::worker::WORKER_CONFIG.load_full();
+ store_pull_query(&wc).await;
+ }
+ Ok(())
+}
+
+pub async fn load_workspace_fairness_max_percent(db: &DB) -> error::Result<()> {
+ // Distinguish three outcomes:
+ // - `Err(_)`: transient DB issue. Leave the atomic alone (don't clobber a known-good value
+ // because of a network blip during a notify-event propagation).
+ // - `Ok(None)` or `Ok(Some(invalid))`: setting is unset / explicitly cleared / corrupt.
+ // Restore the default so a deletion via the admin UI actually takes effect at runtime
+ // instead of leaving the stale in-memory value pinned until restart.
+ // - `Ok(Some(valid))`: clamp and store.
+ match load_value_from_global_settings(db, WORKSPACE_FAIRNESS_MAX_PERCENT_SETTING).await? {
+ Some(serde_json::Value::Number(n)) => {
+ let v = n
+ .as_u64()
+ .map(|u| u.clamp(1, 100) as u32)
+ .unwrap_or(WORKSPACE_FAIRNESS_MAX_PERCENT_DEFAULT);
+ WORKSPACE_FAIRNESS_MAX_PERCENT.store(v, Ordering::Relaxed);
+ }
+ _ => {
+ WORKSPACE_FAIRNESS_MAX_PERCENT
+ .store(WORKSPACE_FAIRNESS_MAX_PERCENT_DEFAULT, Ordering::Relaxed);
+ }
+ }
+ Ok(())
+}
+
+pub async fn load_workspace_fairness_duration_secs(db: &DB) -> error::Result<()> {
+ // See `load_workspace_fairness_max_percent` for the Err / None / invalid policy.
+ match load_value_from_global_settings(db, WORKSPACE_FAIRNESS_DURATION_SECS_SETTING).await? {
+ Some(serde_json::Value::Number(n)) => {
+ // Clamp to the safe range before narrowing. The downstream `u32 -> i32` cast in
+ // `workspace_fairness::refresh_overloaded` makes any value above `i32::MAX` toxic
+ // (sign flip → negative interval → silent disable of the completed-jobs scan).
+ let v = n
+ .as_u64()
+ .map(|u| u.clamp(1, WORKSPACE_FAIRNESS_DURATION_SECS_MAX) as u32)
+ .unwrap_or(WORKSPACE_FAIRNESS_DURATION_SECS_DEFAULT);
+ WORKSPACE_FAIRNESS_DURATION_SECS.store(v, Ordering::Relaxed);
+ }
+ _ => {
+ WORKSPACE_FAIRNESS_DURATION_SECS
+ .store(WORKSPACE_FAIRNESS_DURATION_SECS_DEFAULT, Ordering::Relaxed);
+ }
+ }
+ Ok(())
+}
+
+pub async fn load_workspace_fairness_min_total(db: &DB) -> error::Result<()> {
+ // See `load_workspace_fairness_max_percent` for the Err / None / invalid policy.
+ match load_value_from_global_settings(db, WORKSPACE_FAIRNESS_MIN_TOTAL_SETTING).await? {
+ Some(serde_json::Value::Number(n)) => {
+ // Clamp before narrowing — same reasoning as `_duration_secs`, just for the
+ // counting threshold rather than the interval.
+ let v = n
+ .as_u64()
+ .map(|u| u.min(WORKSPACE_FAIRNESS_MIN_TOTAL_MAX) as u32)
+ .unwrap_or(WORKSPACE_FAIRNESS_MIN_TOTAL_DEFAULT);
+ WORKSPACE_FAIRNESS_MIN_TOTAL.store(v, Ordering::Relaxed);
+ }
+ _ => {
+ WORKSPACE_FAIRNESS_MIN_TOTAL
+ .store(WORKSPACE_FAIRNESS_MIN_TOTAL_DEFAULT, Ordering::Relaxed);
+ }
+ }
+ Ok(())
+}
+
pub async fn load_fork_workspace_tag_append_fork_suffix(db: &DB) -> error::Result<()> {
let value =
load_value_from_global_settings(db, FORK_WORKSPACE_TAG_APPEND_FORK_SUFFIX_SETTING).await;
@@ -886,11 +1035,13 @@ pub async fn reload_otel_tracing_proxy_setting(conn: &Connection) {
let mut current = OTEL_TRACING_PROXY_SETTINGS.write().await;
if current.enabled != new_settings.enabled
|| current.enabled_languages != new_settings.enabled_languages
+ || current.no_proxy_hosts != new_settings.no_proxy_hosts
{
tracing::info!(
- "OTEL tracing proxy settings changed: enabled={}, languages={:?}",
+ "OTEL tracing proxy settings changed: enabled={}, languages={:?}, no_proxy_hosts={:?}",
new_settings.enabled,
- new_settings.enabled_languages
+ new_settings.enabled_languages,
+ new_settings.no_proxy_hosts,
);
*current = new_settings;
}
@@ -1604,6 +1755,16 @@ pub async fn reload_uv_exclude_newer_setting(conn: &Connection) {
.await;
}
+pub async fn reload_uv_python_install_mirror_setting(conn: &Connection) {
+ reload_option_setting_with_tracing(
+ conn,
+ UV_PYTHON_INSTALL_MIRROR_SETTING,
+ "UV_PYTHON_INSTALL_MIRROR",
+ UV_PYTHON_INSTALL_MIRROR.clone(),
+ )
+ .await;
+}
+
pub async fn reload_bun_install_min_release_age_setting(conn: &Connection) {
reload_option_setting_with_tracing(
conn,
@@ -1839,6 +2000,21 @@ pub async fn reload_delete_logs_periodically_setting(conn: &Connection) {
}
}
+pub async fn reload_store_audit_logs_s3_setting(conn: &Connection) {
+ match load_setting_value::(
+ conn,
+ STORE_AUDIT_LOGS_S3_SETTING,
+ "STORE_AUDIT_LOGS_S3",
+ false,
+ |x| x,
+ )
+ .await
+ {
+ Ok(v) => STORE_AUDIT_LOGS_S3.store(v, Ordering::Relaxed),
+ Err(e) => tracing::error!("Error reloading store_audit_logs_s3 setting: {:?}", e),
+ }
+}
+
pub async fn reload_job_default_timeout_setting(conn: &Connection) {
reload_option_setting_with_tracing(
conn,
@@ -1849,6 +2025,26 @@ pub async fn reload_job_default_timeout_setting(conn: &Connection) {
.await;
}
+pub async fn reload_nsjail_tmpfs_size_setting(conn: &Connection) {
+ reload_option_setting_with_tracing(
+ conn,
+ NSJAIL_TMPFS_SIZE_MB_SETTING,
+ "NSJAIL_TMPFS_SIZE_MB",
+ NSJAIL_TMPFS_SIZE_MB.clone(),
+ )
+ .await;
+}
+
+pub async fn reload_nsjail_tmp_backing_setting(conn: &Connection) {
+ reload_option_setting_with_tracing(
+ conn,
+ NSJAIL_TMP_BACKING_SETTING,
+ "NSJAIL_TMP_BACKING",
+ NSJAIL_TMP_BACKING.clone(),
+ )
+ .await;
+}
+
pub async fn reload_job_isolation_setting(conn: &Connection) {
let value =
match load_value_from_global_settings_with_conn(conn, JOB_ISOLATION_SETTING, true).await {
@@ -2373,7 +2569,19 @@ pub async fn monitor_db(
let verify_license_key_f = async {
#[cfg(feature = "enterprise")]
if !initial_load {
- verify_license_key().await;
+ verify_license_key(conn.as_sql()).await;
+ }
+ };
+
+ let enforce_offline_caps_f = async {
+ #[cfg(feature = "enterprise")]
+ if server_mode && !initial_load {
+ if let Some(db) = conn.as_sql() {
+ // Cheap: one query for workers active in the last 2 minutes.
+ if let Err(e) = windmill_common::ee_oss::enforce_offline_caps(db).await {
+ tracing::error!("Failed to enforce offline license caps: {e:#}");
+ }
+ }
}
};
@@ -2497,6 +2705,26 @@ pub async fn monitor_db(
}
};
+ // run every hour (120 iterations * 30s = 3600s)
+ let cleanup_stale_server_heartbeats_f = async {
+ if server_mode && iteration.is_some() && iteration.as_ref().unwrap().should_run(120) {
+ if let Some(db) = conn.as_sql() {
+ match windmill_api::cleanup_stale_server_heartbeats(db).await {
+ Ok(count) if count > 0 => {
+ tracing::info!(
+ "Deleted {} stale server_heartbeat background_task_state rows",
+ count
+ );
+ }
+ Err(e) => {
+ tracing::error!("Error cleaning up stale server_heartbeat rows: {:?}", e);
+ }
+ _ => {}
+ }
+ }
+ }
+ };
+
// run every hour (120 iterations * 30s = 3600s)
let manage_audit_partitions_f = async {
if server_mode && iteration.is_some() && iteration.as_ref().unwrap().should_run(120) {
@@ -2506,6 +2734,23 @@ pub async fn monitor_db(
}
};
+ // run every ~60s (2 iterations * 30s). Enterprise feature: core logic is
+ // in `crate::ee` (OSS gets a no-op stub); gated on a valid Enterprise
+ // license, mirroring how `audit_log()` itself is license-aware.
+ let export_audit_logs_to_object_store_f = async {
+ #[cfg(feature = "parquet")]
+ if server_mode && iteration.is_some() && iteration.as_ref().unwrap().should_run(2) {
+ if let Some(db) = conn.as_sql() {
+ if matches!(
+ windmill_common::ee_oss::get_license_plan().await,
+ windmill_common::ee_oss::LicensePlan::Enterprise
+ ) {
+ crate::ee_oss::export_audit_logs_to_object_store(&db).await;
+ }
+ }
+ }
+ };
+
let cleanup_scheduled_job_deletions_f = async {
#[cfg(feature = "enterprise")]
if server_mode && !initial_load {
@@ -2522,6 +2767,7 @@ pub async fn monitor_db(
vacuum_queue_f,
expose_queue_metrics_f,
verify_license_key_f,
+ enforce_offline_caps_f,
worker_groups_alerts_f,
jobs_waiting_alerts_f,
low_disk_alerts_f,
@@ -2537,7 +2783,9 @@ pub async fn monitor_db(
native_triggers_sync_f,
cleanup_notify_events_f,
check_expiring_tokens_f,
+ cleanup_stale_server_heartbeats_f,
manage_audit_partitions_f,
+ export_audit_logs_to_object_store_f,
cleanup_scheduled_job_deletions_f,
);
}
@@ -2853,6 +3101,11 @@ pub async fn reload_base_url_setting(conn: &Connection) -> error::Result<()> {
IS_SECURE.store(is_secure, Ordering::Relaxed);
+ #[cfg(feature = "enterprise")]
+ {
+ crate::ee_oss::verify_license_key(conn.as_sql()).await;
+ }
+
Ok(())
}
@@ -3404,7 +3657,7 @@ async fn handle_zombie_flows(db: &DB) -> error::Result<()> {
FROM v2_job_queue q JOIN v2_job j USING (id) LEFT JOIN v2_job_runtime r USING (id) LEFT JOIN v2_job_status s USING (id)
LEFT JOIN worker_ping wp ON wp.worker = q.worker
WHERE q.running = true AND q.suspend = 0 AND q.suspend_until IS null AND q.scheduled_for <= now()
- AND (j.kind = 'flow' OR j.kind = 'flowpreview' OR j.kind = 'flownode')
+ AND (j.kind = 'flow' OR j.kind = 'flowpreview' OR j.kind = 'flownode' OR j.kind = 'singlestepflow')
AND r.ping IS NOT NULL AND r.ping < NOW() - ($1 || ' seconds')::interval
AND q.canceled_by IS NULL
diff --git a/backend/tests/app_custom_path_cross_workspace.rs b/backend/tests/app_custom_path_cross_workspace.rs
new file mode 100644
index 0000000000..5b0002bb05
--- /dev/null
+++ b/backend/tests/app_custom_path_cross_workspace.rs
@@ -0,0 +1,120 @@
+//! Regression test for the cross-workspace custom_path conflict.
+//!
+//! When custom paths are instance-global (CLOUD_HOSTED unset and
+//! `app_workspaced_route` off — the default for dedicated instances), a
+//! custom_path is a single global route slot. The uniqueness check correctly
+//! blocks two apps from claiming it, including the same logical app deployed
+//! to two workspaces (staging/prod, git-sync). The bug was that the error
+//! ("App with custom path already exists") gave the operator no idea
+//! where the conflicting copy lived. This test pins down:
+//! - a single-workspace edit keeping its own custom_path still succeeds
+//! (the app's own row is excluded),
+//! - a real conflict is still rejected, and
+//! - the error now names the conflicting app's path and workspace.
+
+use serde_json::json;
+use sqlx::{Pool, Postgres};
+use windmill_test_utils::*;
+
+fn client() -> reqwest::Client {
+ reqwest::Client::new()
+}
+
+fn authed(builder: reqwest::RequestBuilder, token: &str) -> reqwest::RequestBuilder {
+ builder.header("Authorization", format!("Bearer {}", token))
+}
+
+fn new_app(path: &str, custom_path: &str) -> serde_json::Value {
+ json!({
+ "path": path,
+ "summary": "Test app",
+ "value": { "type": "rawapp", "inline_script": null },
+ "policy": { "execution_mode": "anonymous", "triggerables": {} },
+ "custom_path": custom_path
+ })
+}
+
+#[sqlx::test(fixtures("app_custom_path_cross_workspace"))]
+async fn test_custom_path_cross_workspace_deploy(db: Pool) -> anyhow::Result<()> {
+ initialize_tracing().await;
+
+ let server = ApiServer::start(db.clone()).await?;
+ let port = server.addr.port();
+ let ws_a = format!("http://localhost:{port}/api/w/test-workspace");
+ let ws_b = format!("http://localhost:{port}/api/w/test-workspace-2");
+
+ let app_path = "f/Newsletter/newsletter_composer";
+ let custom_path = "newsletter";
+
+ // 1. Create the app with a custom path in workspace A.
+ let resp = authed(client().post(format!("{ws_a}/apps/create")), "SECRET_TOKEN")
+ .json(&new_app(app_path, custom_path))
+ .send()
+ .await?;
+ assert_eq!(
+ resp.status(),
+ 201,
+ "create app in ws A should succeed: {}",
+ resp.text().await?
+ );
+
+ // 2. Editing the app in its own workspace, keeping the same custom path,
+ // must still succeed — the app's own row is excluded from the check.
+ // (This is the common single-workspace deploy; it must not regress.)
+ let resp = authed(
+ client().post(format!("{ws_a}/apps/update/{app_path}")),
+ "SECRET_TOKEN",
+ )
+ .json(&json!({
+ "summary": "Test app (edited)",
+ "value": { "type": "rawapp", "inline_script": null },
+ "policy": { "execution_mode": "anonymous", "triggerables": {} },
+ "custom_path": custom_path
+ }))
+ .send()
+ .await?;
+ assert_eq!(
+ resp.status(),
+ 200,
+ "editing an app in its own workspace keeping its custom path must succeed: {}",
+ resp.text().await?
+ );
+
+ // 3. Deploying the same app (same path) to a second workspace is a real
+ // conflict in global mode (one global route slot). It must be rejected,
+ // and the error must name the conflicting workspace + app so the
+ // operator knows what to resolve.
+ let resp = authed(client().post(format!("{ws_b}/apps/create")), "SECRET_TOKEN")
+ .json(&new_app(app_path, custom_path))
+ .send()
+ .await?;
+ let status = resp.status();
+ let body = resp.text().await?;
+ assert_eq!(
+ status, 400,
+ "same custom path in another workspace is a global conflict: {body}"
+ );
+ assert!(
+ body.contains("test-workspace") && body.contains(app_path),
+ "error must name the conflicting workspace and app, got: {body}"
+ );
+
+ // 4. A genuinely different app claiming the in-use custom path is still
+ // rejected, with the same actionable message.
+ let resp = authed(client().post(format!("{ws_a}/apps/create")), "SECRET_TOKEN")
+ .json(&new_app("f/Other/other_app", custom_path))
+ .send()
+ .await?;
+ let status = resp.status();
+ let body = resp.text().await?;
+ assert_eq!(
+ status, 400,
+ "a different app must not steal an in-use custom path: {body}"
+ );
+ assert!(
+ body.contains(app_path),
+ "error must name the app already using the custom path, got: {body}"
+ );
+
+ Ok(())
+}
diff --git a/backend/tests/app_preview_auth.rs b/backend/tests/app_preview_auth.rs
new file mode 100644
index 0000000000..4653303392
--- /dev/null
+++ b/backend/tests/app_preview_auth.rs
@@ -0,0 +1,258 @@
+//! Regression test for the app component preview authorization bypass.
+//!
+//! `POST /api/w/:workspace/apps_u/execute_component/:path` runs in "preview"
+//! mode whenever the client supplies `force_viewer_static_fields`. In that
+//! mode it accepts request-supplied `raw_code` and enqueues it as a
+//! `Viewer`-mode job — i.e. it is the app-editor equivalent of
+//! `/jobs/run/preview`. The bug was that this branch did not re-apply the
+//! guards `/jobs/run/preview` enforces for arbitrary code execution, so an
+//! authenticated Operator (a run-only user who must not be able to create
+//! scripts/apps or run preview jobs) could enqueue arbitrary worker code with
+//! a single request, escaping the Operator restriction entirely.
+//!
+//! This test pins down:
+//! - an Operator is rejected from preview mode (the core fix; pre-fix this
+//! enqueued a job and returned 200),
+//! - a regular non-operator member can still run an editor preview (the fix
+//! must not over-block the legitimate editor flow),
+//! - preview is confined to paths the caller can read (defense-in-depth
+//! against scoped tokens / cross-namespace preview), and
+//! - run mode (no `force_viewer_static_fields`) is unaffected by the guard.
+
+use serde_json::json;
+use sqlx::{Pool, Postgres};
+use windmill_test_utils::*;
+
+fn client() -> reqwest::Client {
+ reqwest::Client::new()
+}
+
+fn authed(builder: reqwest::RequestBuilder, token: &str) -> reqwest::RequestBuilder {
+ builder.header("Authorization", format!("Bearer {}", token))
+}
+
+/// A preview request: `force_viewer_static_fields` present + inline `raw_code`.
+/// This is the exact shape an attacker (or the editor) sends.
+fn preview_body(app_path: &str) -> serde_json::Value {
+ json!({
+ "args": {},
+ "component": "comp",
+ "raw_code": {
+ "language": "deno",
+ "content": "export function main() { return \"pwned\"; }",
+ "path": format!("{}/comp", app_path)
+ },
+ "force_viewer_static_fields": {}
+ })
+}
+
+#[sqlx::test(fixtures("base", "app_preview_auth"))]
+async fn test_app_preview_authorization(db: Pool) -> anyhow::Result<()> {
+ initialize_tracing().await;
+
+ let server = ApiServer::start(db.clone()).await?;
+ let port = server.addr.port();
+ let base = format!("http://localhost:{port}/api/w/test-workspace/apps_u/execute_component");
+
+ // 1. CORE REGRESSION: an Operator sends a preview request in their own
+ // namespace (so the *only* thing that can reject them is the Operator
+ // check itself). Pre-fix this returned 200 with an enqueued job UUID;
+ // post-fix it must be rejected.
+ let resp = authed(
+ client().post(format!("{base}/u/operator-user/myapp")),
+ "OPERATOR_TOKEN",
+ )
+ .json(&preview_body("u/operator-user/myapp"))
+ .send()
+ .await?;
+ let status = resp.status();
+ let body = resp.text().await?;
+ assert_eq!(
+ status, 401,
+ "Operator must be rejected from app preview (got {status}): {body}"
+ );
+ assert!(
+ body.contains("Operators cannot run preview jobs"),
+ "rejection must be the operator guard, got: {body}"
+ );
+
+ // 2. The fix must NOT over-block the legitimate editor flow: a regular
+ // non-operator member previewing in their own namespace still works
+ // (the endpoint returns the enqueued job UUID before any worker runs).
+ let resp = authed(
+ client().post(format!("{base}/u/test-user-2/myapp")),
+ "SECRET_TOKEN_2",
+ )
+ .json(&preview_body("u/test-user-2/myapp"))
+ .send()
+ .await?;
+ let status = resp.status();
+ let body = resp.text().await?;
+ assert!(
+ status.is_success(),
+ "non-operator editor preview must still succeed (got {status}): {body}"
+ );
+ assert!(
+ uuid::Uuid::parse_str(body.trim()).is_ok(),
+ "successful preview must return a job UUID, got: {body}"
+ );
+
+ // 3. Inline `raw_code` preview is deliberately NOT path-gated: a
+ // non-operator can already run arbitrary inline code via
+ // `/jobs/run/preview`, so the app URL path string is irrelevant for the
+ // inline case. This pins that decision so an over-restrictive path check
+ // is not re-added for inline previews.
+ let resp = authed(
+ client().post(format!("{base}/u/test-user/secretapp")),
+ "SECRET_TOKEN_2",
+ )
+ .json(&preview_body("u/test-user/secretapp"))
+ .send()
+ .await?;
+ let status = resp.status();
+ let body = resp.text().await?;
+ assert!(
+ status.is_success(),
+ "inline raw_code preview must not be path-gated (got {status}): {body}"
+ );
+ assert!(
+ uuid::Uuid::parse_str(body.trim()).is_ok(),
+ "inline preview should enqueue a job UUID, got: {body}"
+ );
+
+ // 4. Run mode (no `force_viewer_static_fields`) is unaffected by the new
+ // preview guard: an Operator hitting a deployed-app path still follows
+ // the pre-existing policy lookup (here: the app does not exist -> 404),
+ // proving the guard only gates preview mode.
+ let resp = authed(
+ client().post(format!("{base}/u/operator-user/nonexistent")),
+ "OPERATOR_TOKEN",
+ )
+ .json(&json!({
+ "args": {},
+ "component": "comp",
+ "raw_code": {
+ "language": "deno",
+ "content": "export function main() { return 1; }",
+ "path": "u/operator-user/nonexistent/comp"
+ }
+ }))
+ .send()
+ .await?;
+ let status = resp.status();
+ let body = resp.text().await?;
+ assert_eq!(
+ status, 404,
+ "run mode must be unchanged (deployed app lookup -> 404, not the preview guard); got {status}: {body}"
+ );
+
+ // 5. Defense-in-depth: the guard must check the *runnable* being previewed,
+ // not just the app URL path. A caller pairs an allowed app path
+ // (`u/test-user-2/myapp`, own namespace) with a `path` pointing at a
+ // deployed runnable in another user's namespace. Without checking the
+ // runnable path this would resolve `script/u/test-user/private` with the
+ // root DB handle and enqueue it; it must be rejected by the path check.
+ let resp = authed(
+ client().post(format!("{base}/u/test-user-2/myapp")),
+ "SECRET_TOKEN_2",
+ )
+ .json(&json!({
+ "args": {},
+ "component": "comp",
+ "path": "script/u/test-user/private",
+ "force_viewer_static_fields": {}
+ }))
+ .send()
+ .await?;
+ let status = resp.status();
+ let body = resp.text().await?;
+ assert_eq!(
+ status, 400,
+ "preview targeting a runnable outside the caller's namespace must be rejected even with an allowed app path (got {status}): {body}"
+ );
+
+ // 6. Defense-in-depth: a persisted inline-script preview selects code by the
+ // caller-controlled `app_script` id. Pairing an allowed app path with an
+ // id owned by another (private) app must be rejected — without the
+ // id-ownership check the worker would fetch and run that app's code.
+ let resp = authed(
+ client().post(format!("{base}/u/test-user-2/myapp")),
+ "SECRET_TOKEN_2",
+ )
+ .json(&json!({
+ "args": {},
+ "component": "comp",
+ "id": 999777,
+ "raw_code": {
+ "language": "deno",
+ "content": "export function main() { return 1; }",
+ "path": "u/test-user-2/myapp/comp"
+ },
+ "force_viewer_static_fields": {}
+ }))
+ .send()
+ .await?;
+ let status = resp.status();
+ let body = resp.text().await?;
+ assert_eq!(
+ status, 400,
+ "preview with an app_script id owned by another app must be rejected (got {status}): {body}"
+ );
+
+ // 7. The id-ownership check must NOT over-block a legitimate persisted
+ // inline-script preview: an id owned by an app in the caller's own
+ // namespace passes the guard and enqueues (returns a job UUID).
+ let resp = authed(
+ client().post(format!("{base}/u/test-user-2/ownapp")),
+ "SECRET_TOKEN_2",
+ )
+ .json(&json!({
+ "args": {},
+ "component": "comp",
+ "id": 999778,
+ "raw_code": {
+ "language": "deno",
+ "content": "export function main() { return 1; }",
+ "path": "u/test-user-2/ownapp/comp"
+ },
+ "force_viewer_static_fields": {}
+ }))
+ .send()
+ .await?;
+ let status = resp.status();
+ let body = resp.text().await?;
+ assert!(
+ status.is_success(),
+ "persisted preview for an app the caller owns must still succeed (got {status}): {body}"
+ );
+ assert!(
+ uuid::Uuid::parse_str(body.trim()).is_ok(),
+ "successful persisted preview must return a job UUID, got: {body}"
+ );
+
+ // 8. Scope escalation: a token scoped to `apps:run` (but not `jobs:run`)
+ // can reach this route (it maps to the `apps` scope domain) and is not an
+ // Operator, but must NOT be able to enqueue arbitrary preview `raw_code`.
+ // `/jobs/run/preview` requires `jobs:run` for exactly this reason; the
+ // app preview path must enforce the same. Without the `jobs:run` check
+ // this enqueues a job (returns a UUID); with it, it is rejected (403).
+ let resp = authed(
+ client().post(format!("{base}/u/test-user-2/myapp")),
+ "APPS_RUN_TOKEN",
+ )
+ .json(&preview_body("u/test-user-2/myapp"))
+ .send()
+ .await?;
+ let status = resp.status();
+ let body = resp.text().await?;
+ assert_eq!(
+ status, 403,
+ "apps:run-scoped token must not escalate to arbitrary preview code (got {status}): {body}"
+ );
+ assert!(
+ body.contains("jobs:run"),
+ "rejection must be the jobs:run scope gate, got: {body}"
+ );
+
+ Ok(())
+}
diff --git a/backend/tests/batch_rerun.rs b/backend/tests/batch_rerun.rs
new file mode 100644
index 0000000000..b750379834
--- /dev/null
+++ b/backend/tests/batch_rerun.rs
@@ -0,0 +1,615 @@
+//! Tests for `/jobs/run/batch_rerun_jobs` and `/jobs/list_selected_job_groups`.
+//!
+//! These cover both the long-existing baseline behavior (regular Script and Flow
+//! jobs reran with default args, `use_latest_version=true`, and `input_transforms`)
+//! and the SingleStepFlow projection paths added in the PR that introduced this
+//! file. The endpoint had zero coverage before — every change here exists because
+//! a code reviewer or reader caught it; the tests exist so the next reader doesn't
+//! have to.
+
+use serde_json::json;
+use sqlx::{Pool, Postgres};
+use uuid::Uuid;
+
+use windmill_common::{
+ flows::Retry,
+ jobs::{JobKind, JobPayload},
+ runnable_settings::{ConcurrencySettings, DebouncingSettings},
+ scripts::{ScriptHash, ScriptLang},
+};
+
+use windmill_test_utils::*;
+
+const WORKSPACE: &str = "test-workspace";
+const SCRIPT_PATH: &str = "u/test-user/rerun_script";
+const SCRIPT_HASH: i64 = 1111111111;
+const FLOW_PATH: &str = "u/test-user/rerun_flow";
+const FLOW_VERSION: i64 = 2222222222;
+
+/// Mark a queued job as completed with success — needed so it's eligible for
+/// `list_selected_job_groups` / `batch_rerun_jobs` (both join `v2_job_completed`).
+async fn complete_job(db: &Pool, job_id: Uuid) -> anyhow::Result<()> {
+ sqlx::query(
+ "INSERT INTO v2_job_completed (workspace_id, id, result, status, duration_ms, started_at, completed_at)
+ VALUES ($1, $2, '{}'::jsonb, 'success', 0, now(), now())",
+ )
+ .bind(WORKSPACE)
+ .bind(job_id)
+ .execute(db)
+ .await?;
+ Ok(())
+}
+
+fn script_payload() -> JobPayload {
+ JobPayload::ScriptHash {
+ hash: ScriptHash(SCRIPT_HASH),
+ path: SCRIPT_PATH.to_string(),
+ cache_ttl: None,
+ cache_ignore_s3_path: None,
+ dedicated_worker: None,
+ language: ScriptLang::Deno,
+ priority: None,
+ apply_preprocessor: false,
+ concurrency_settings: ConcurrencySettings::default().into(),
+ debouncing_settings: DebouncingSettings::default(),
+ labels: None,
+ }
+}
+
+fn flow_payload() -> JobPayload {
+ JobPayload::Flow {
+ path: FLOW_PATH.to_string(),
+ dedicated_worker: None,
+ apply_preprocessor: false,
+ version: FLOW_VERSION,
+ labels: None,
+ }
+}
+
+fn ssf_script_payload(hash: Option, retry: Option) -> JobPayload {
+ JobPayload::SingleStepFlow {
+ path: SCRIPT_PATH.to_string(),
+ hash,
+ flow_version: None,
+ args: Default::default(),
+ retry,
+ error_handler_path: None,
+ error_handler_args: None,
+ skip_handler: None,
+ cache_ttl: None,
+ cache_ignore_s3_path: None,
+ priority: None,
+ tag_override: None,
+ trigger_path: None,
+ apply_preprocessor: false,
+ concurrency_settings: ConcurrencySettings::default(),
+ debouncing_settings: DebouncingSettings::default(),
+ }
+}
+
+fn ssf_flow_payload() -> JobPayload {
+ JobPayload::SingleStepFlow {
+ path: FLOW_PATH.to_string(),
+ hash: None,
+ flow_version: Some(FLOW_VERSION),
+ args: Default::default(),
+ retry: None,
+ error_handler_path: None,
+ error_handler_args: None,
+ skip_handler: None,
+ cache_ttl: None,
+ cache_ignore_s3_path: None,
+ priority: None,
+ tag_override: None,
+ trigger_path: None,
+ apply_preprocessor: false,
+ concurrency_settings: ConcurrencySettings::default(),
+ debouncing_settings: DebouncingSettings::default(),
+ }
+}
+
+/// Push + complete a job so it's eligible for batch rerun.
+async fn push_completed(
+ db: &Pool,
+ payload: JobPayload,
+ args: Vec<(&str, serde_json::Value)>,
+) -> anyhow::Result {
+ let mut runner = RunJob::from(payload);
+ for (k, v) in args {
+ runner = runner.arg(k.to_string(), v);
+ }
+ let id = runner.push(db).await;
+ complete_job(db, id).await?;
+ Ok(id)
+}
+
+/// POST `/jobs/run/batch_rerun_jobs` and parse the SSE-style line-per-result
+/// stream into a list of (uuid, error?) pairs.
+async fn batch_rerun(
+ client: &windmill_api_client::Client,
+ body: serde_json::Value,
+) -> anyhow::Result>> {
+ let response = client
+ .client()
+ .post(format!(
+ "{}/w/{}/jobs/run/batch_rerun_jobs",
+ client.baseurl(),
+ WORKSPACE
+ ))
+ .json(&body)
+ .send()
+ .await?;
+ assert!(
+ response.status().is_success(),
+ "batch_rerun_jobs returned {}",
+ response.status()
+ );
+ let body = response.text().await?;
+ Ok(body
+ .lines()
+ .filter(|l| !l.is_empty())
+ .map(|l| {
+ if let Some(err) = l.strip_prefix("Error: ") {
+ Err(err.to_string())
+ } else {
+ Ok(Uuid::parse_str(l.trim()).expect("rerun line should be a UUID"))
+ }
+ })
+ .collect())
+}
+
+/// Read kind + args of a freshly-rerun job.
+async fn rerun_job(db: &Pool, id: Uuid) -> anyhow::Result<(JobKind, serde_json::Value)> {
+ let row = sqlx::query!(
+ r#"SELECT kind AS "kind: JobKind", args::text AS "args!"
+ FROM v2_job WHERE id = $1"#,
+ id
+ )
+ .fetch_one(db)
+ .await?;
+ Ok((row.kind, serde_json::from_str(&row.args)?))
+}
+
+// ---------------------------------------------------------------------------
+// Baseline regression — regular Script and Flow rerun.
+
+/// Default-mode rerun on a regular Script: new Script job inherits original args.
+#[sqlx::test(fixtures("base", "batch_rerun"))]
+async fn batch_rerun_script_default(db: Pool) -> anyhow::Result<()> {
+ initialize_tracing().await;
+ let server = ApiServer::start(db.clone()).await?;
+ let client = windmill_api_client::create_client(
+ &format!("http://localhost:{}", server.addr.port()),
+ "SECRET_TOKEN".to_string(),
+ );
+
+ let original = push_completed(&db, script_payload(), vec![("name", json!("orig"))]).await?;
+
+ let results = batch_rerun(
+ &client,
+ json!({
+ "job_ids": [original],
+ "script_options_by_path": {},
+ "flow_options_by_path": {},
+ }),
+ )
+ .await?;
+ assert_eq!(results.len(), 1, "expected one rerun");
+ let new_id = results[0].as_ref().expect("rerun should succeed").clone();
+ let (kind, args) = rerun_job(&db, new_id).await?;
+ assert_eq!(kind, JobKind::Script);
+ assert_eq!(args, json!({"name": "orig"}));
+ Ok(())
+}
+
+/// `use_latest_version=true` reruns regular Script via path-based push.
+#[sqlx::test(fixtures("base", "batch_rerun"))]
+async fn batch_rerun_script_latest_version(db: Pool) -> anyhow::Result<()> {
+ initialize_tracing().await;
+ let server = ApiServer::start(db.clone()).await?;
+ let client = windmill_api_client::create_client(
+ &format!("http://localhost:{}", server.addr.port()),
+ "SECRET_TOKEN".to_string(),
+ );
+
+ let original = push_completed(&db, script_payload(), vec![("name", json!("orig"))]).await?;
+
+ let results = batch_rerun(
+ &client,
+ json!({
+ "job_ids": [original],
+ "script_options_by_path": {
+ SCRIPT_PATH: { "use_latest_version": true }
+ },
+ "flow_options_by_path": {},
+ }),
+ )
+ .await?;
+ let new_id = results[0].as_ref().expect("rerun should succeed").clone();
+ let (kind, args) = rerun_job(&db, new_id).await?;
+ assert_eq!(kind, JobKind::Script);
+ assert_eq!(args, json!({"name": "orig"}));
+ Ok(())
+}
+
+/// `input_transforms` static value overrides original args on Script rerun.
+#[sqlx::test(fixtures("base", "batch_rerun"))]
+async fn batch_rerun_script_static_transform(db: Pool) -> anyhow::Result<()> {
+ initialize_tracing().await;
+ let server = ApiServer::start(db.clone()).await?;
+ let client = windmill_api_client::create_client(
+ &format!("http://localhost:{}", server.addr.port()),
+ "SECRET_TOKEN".to_string(),
+ );
+
+ let original = push_completed(&db, script_payload(), vec![("name", json!("orig"))]).await?;
+
+ let results = batch_rerun(
+ &client,
+ json!({
+ "job_ids": [original],
+ "script_options_by_path": {
+ SCRIPT_PATH: {
+ "input_transforms": { "name": { "type": "static", "value": "\"X\"" } }
+ }
+ },
+ "flow_options_by_path": {},
+ }),
+ )
+ .await?;
+ let new_id = results[0].as_ref().expect("rerun should succeed").clone();
+ let (_, args) = rerun_job(&db, new_id).await?;
+ assert_eq!(
+ args,
+ json!({"name": "\"X\""}),
+ "static transform should override original args"
+ );
+ Ok(())
+}
+
+/// Regular Flow rerun: new Flow job inherits original args.
+#[sqlx::test(fixtures("base", "batch_rerun"))]
+async fn batch_rerun_flow_default(db: Pool) -> anyhow::Result<()> {
+ initialize_tracing().await;
+ let server = ApiServer::start(db.clone()).await?;
+ let client = windmill_api_client::create_client(
+ &format!("http://localhost:{}", server.addr.port()),
+ "SECRET_TOKEN".to_string(),
+ );
+
+ let original = push_completed(&db, flow_payload(), vec![("name", json!("orig-flow"))]).await?;
+
+ let results = batch_rerun(
+ &client,
+ json!({
+ "job_ids": [original],
+ "script_options_by_path": {},
+ "flow_options_by_path": {},
+ }),
+ )
+ .await?;
+ let new_id = results[0].as_ref().expect("rerun should succeed").clone();
+ let (kind, args) = rerun_job(&db, new_id).await?;
+ assert_eq!(kind, JobKind::Flow);
+ assert_eq!(args, json!({"name": "orig-flow"}));
+ Ok(())
+}
+
+/// `input_transforms` override original args on Flow rerun (path-based,
+/// frontend forces use_latest_version=true for flow).
+#[sqlx::test(fixtures("base", "batch_rerun"))]
+async fn batch_rerun_flow_static_transform(db: Pool) -> anyhow::Result<()> {
+ initialize_tracing().await;
+ let server = ApiServer::start(db.clone()).await?;
+ let client = windmill_api_client::create_client(
+ &format!("http://localhost:{}", server.addr.port()),
+ "SECRET_TOKEN".to_string(),
+ );
+
+ let original = push_completed(&db, flow_payload(), vec![("name", json!("orig"))]).await?;
+
+ let results = batch_rerun(
+ &client,
+ json!({
+ "job_ids": [original],
+ "script_options_by_path": {},
+ "flow_options_by_path": {
+ FLOW_PATH: {
+ "use_latest_version": true,
+ "input_transforms": { "name": { "type": "static", "value": "\"FX\"" } }
+ }
+ },
+ }),
+ )
+ .await?;
+ let new_id = results[0].as_ref().expect("rerun should succeed").clone();
+ let (_, args) = rerun_job(&db, new_id).await?;
+ assert_eq!(args, json!({"name": "\"FX\""}));
+ Ok(())
+}
+
+// ---------------------------------------------------------------------------
+// SingleStepFlow projection — the bug class this PR fixes.
+
+/// Default rerun of a script-wrapped SingleStepFlow lands as a plain Script.
+#[sqlx::test(fixtures("base", "batch_rerun"))]
+async fn batch_rerun_ssf_script_default(db: Pool) -> anyhow::Result<()> {
+ initialize_tracing().await;
+ let server = ApiServer::start(db.clone()).await?;
+ let client = windmill_api_client::create_client(
+ &format!("http://localhost:{}", server.addr.port()),
+ "SECRET_TOKEN".to_string(),
+ );
+
+ let original = push_completed(
+ &db,
+ ssf_script_payload(Some(ScriptHash(SCRIPT_HASH)), None),
+ vec![("name", json!("orig-ssf"))],
+ )
+ .await?;
+
+ let results = batch_rerun(
+ &client,
+ json!({
+ "job_ids": [original],
+ "script_options_by_path": {},
+ "flow_options_by_path": {},
+ }),
+ )
+ .await?;
+ let new_id = results[0].as_ref().expect("SSF should be reruable").clone();
+ let (kind, args) = rerun_job(&db, new_id).await?;
+ // Wrapper unwraps to plain Script (retry policy belongs to the trigger, not to "rerun").
+ assert_eq!(kind, JobKind::Script);
+ assert_eq!(args, json!({"name": "orig-ssf"}));
+ Ok(())
+}
+
+/// `use_latest_version=true` + `input_transforms` on SSF — exercises the
+/// `latest_schema` projection in `batch_rerun_handle_job`. Regression for the
+/// fix in this PR's third commit; without it the transform silently no-ops.
+#[sqlx::test(fixtures("base", "batch_rerun"))]
+async fn batch_rerun_ssf_script_latest_with_transform(db: Pool) -> anyhow::Result<()> {
+ initialize_tracing().await;
+ let server = ApiServer::start(db.clone()).await?;
+ let client = windmill_api_client::create_client(
+ &format!("http://localhost:{}", server.addr.port()),
+ "SECRET_TOKEN".to_string(),
+ );
+
+ let original = push_completed(
+ &db,
+ ssf_script_payload(Some(ScriptHash(SCRIPT_HASH)), None),
+ vec![("name", json!("orig"))],
+ )
+ .await?;
+
+ let results = batch_rerun(
+ &client,
+ json!({
+ "job_ids": [original],
+ "script_options_by_path": {
+ SCRIPT_PATH: {
+ "use_latest_version": true,
+ "input_transforms": { "name": { "type": "static", "value": "\"S\"" } }
+ }
+ },
+ "flow_options_by_path": {},
+ }),
+ )
+ .await?;
+ let new_id = results[0]
+ .as_ref()
+ .expect("SSF rerun should succeed")
+ .clone();
+ let (_, args) = rerun_job(&db, new_id).await?;
+ assert_eq!(args, json!({"name": "\"S\""}));
+ Ok(())
+}
+
+/// Flow-wrapped SingleStepFlow projects to Flow and reruns by path.
+#[sqlx::test(fixtures("base", "batch_rerun"))]
+async fn batch_rerun_ssf_flow(db: Pool) -> anyhow::Result<()> {
+ initialize_tracing().await;
+ let server = ApiServer::start(db.clone()).await?;
+ let client = windmill_api_client::create_client(
+ &format!("http://localhost:{}", server.addr.port()),
+ "SECRET_TOKEN".to_string(),
+ );
+
+ let original = push_completed(
+ &db,
+ ssf_flow_payload(),
+ vec![("name", json!("orig-ssf-flow"))],
+ )
+ .await?;
+
+ let results = batch_rerun(
+ &client,
+ json!({
+ "job_ids": [original],
+ "script_options_by_path": {},
+ "flow_options_by_path": {
+ FLOW_PATH: {
+ "use_latest_version": true,
+ "input_transforms": { "name": { "type": "static", "value": "\"FF\"" } }
+ }
+ },
+ }),
+ )
+ .await?;
+ let new_id = results[0]
+ .as_ref()
+ .expect("SSF flow should be reruable")
+ .clone();
+ let (kind, args) = rerun_job(&db, new_id).await?;
+ assert_eq!(kind, JobKind::Flow);
+ assert_eq!(args, json!({"name": "\"FF\""}));
+ Ok(())
+}
+
+/// Mixed batch (script + flow + SSF-script + SSF-flow) — exercises the
+/// dispatch arms across all four kinds in a single request.
+#[sqlx::test(fixtures("base", "batch_rerun"))]
+async fn batch_rerun_mixed_kinds(db: Pool