get_lint_errors on a raw app without a runnable key now compiles the app's
frontend files and reports where the build broke, so editing app files can
be checked the same way editing a runnable already was. A frontend is a
bundle rather than a set of independent documents, so there is no language
service to query per file — only the compiler's verdict, which also means
files unreachable from the entry point are not covered.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review findings on the headless linter:
- An editor can adopt a model headless linting created, so attachment now
decides who owns a buffer rather than who created it. The editor likewise
applies its own content when adopting an existing model, instead of
displaying whatever was last linted.
- Markers are cleared when an owned model is rewritten: they outlive the
content they describe, and a matching count let the settle loop return
the previous version's diagnostics.
- Eviction no longer drops the URI it is keeping, which had left a live
model untracked and subsequently treated as belonging to an editor.
- A language server that stalls is named in the result rather than
contributing an empty list, and no longer discards what other servers
already reported.
- A client abandoned at its deadline is stopped, so it cannot keep holding
the global command names every later lint of that language needs.
- Deno's import map is built from the path its document actually has.
- Pre-lint network calls are bounded; lints of one URI queue behind each
other, so a hung request would otherwise wedge that item indefinitely.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extends get_lint_errors beyond the TypeScript worker to the language-server
languages. These servers are driven directly rather than through the editor's
clients: pyright and gopls implement LSP 3.17 pull diagnostics and only get
asked for documents visible in an editor, so headless the request has to be
made explicitly. Servers that answer with nothing are still given a bounded
window to publish instead, which is how ruff reports.
A server whose client cannot start — its global command names are taken when
an editor is already running one — is named in the result instead of silently
shrinking it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Diagnostics for python, go, deno and shell are decided by which server runs
and with which initialization options, so headless linting has to build its
clients from the same descriptors. Move them to lint/lspLanguageConfig.ts;
the editor keeps owning the websocket lifecycle and reconnection.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Global chat edits drafts with no editor mounted, so until now it had no way
to type-check what it wrote. The tool resolves draft or deployed code for a
script, an inline flow module, or a raw app backend runnable, lints it
headlessly at the URI the editor would use, and reports unsupported
languages explicitly rather than a misleading clean result.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Lints script, flow-step and raw-app runnable code with no editor mounted, by
driving the same Monaco model, extra libs and acquired types an editor would.
Settling waits for the worker's own diagnostic count rather than a fixed
sleep, which also terminates immediately on clean code, where no marker
change event is ever emitted. Editor.getLintErrors now reads markers through
the same helper, so the two cannot drift apart.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Headless linting needs the same acquired types as the editor, npm
declarations and relative-import models alike. Move the ATA delegate and
token root to lint/typescriptAta.ts; the editor keeps its trigger logic and
supplies the model-revalidation nudge via onLocalFileRegistered.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Headless linting must register the same rt.d.ts resource-type namespace and
custom windmill-client declarations as a mounted editor, since diagnostics
depend on them. Move the apply bodies to lint/typescriptExtraLibs.ts; the
editor keeps its reactive resource() wrapper around the fetch.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Headless linting needs to derive the exact model URI the editor uses;
computeUri/computePath were component-local closures over scriptLang and
lang. Move them to lint/monacoUri.ts with those as explicit parameters.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): sanitize html and svg result rendering
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(frontend): add dompurify to lockfile root deps
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): keep sanitizing rich results on public app surfaces
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(frontend): gate risky app markup on unsandboxed public surfaces
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): derive app markup isolation from the real origin
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* style(frontend): use the design-system danger alert for the markup gate
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(raw-apps): prevent and surface the silent blank screen from an unmounted #root
An `index.tsx` written as a bare `export default function App() {...}` with
no mount call builds and runs without throwing: the preview executes the
bundle against an empty `<div id="root">` and auto-renders nothing, so the
JSX never runs, nothing reaches the console or the runtime-error overlay,
and the app is blank with no diagnostic.
Prevent it: the raw-app system prompt and the in-chat app prompt now state
that `index.tsx` is the mount entrypoint, show the mount shim for React,
Svelte and Vue, and call out that a bare component fails silently.
Surface it: when a build still mounts nothing, the preview harness posts
`emptyRender` and the editor shows an error overlay naming the missing
call. The harness reports only when nothing is on screen AND the app never
looked `#root` up, so an app that mounted but paints nothing yet (a fetch
in flight, an unresolved Suspense) is never flagged; `renderAppeared`
withdraws the overlay if a deferred mount lands late.
The handlers stay dormant until the builder tarball that emits these
messages is pinned via `ui_builder_artifact.json`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(raw-apps): pin the UI builder artifact that emits emptyRender
Activates the "Nothing was mounted" overlay: the pinned tarball predates
the harness change, so the host handlers were dormant until now.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: drop a screenshot accidentally committed at the repo root
Not referenced anywhere; the PR's screenshots are hosted externally.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(raw-apps): make the unmounted-app guidance framework-aware
The detector fires for every raw-app framework, but the overlay and the
prompts named React's `index.tsx` and `createRoot` unconditionally. Svelte
and Vue apps mount from `index.ts` via `mount` / `createApp`, so the
guidance pointed at a nonexistent entrypoint and an unavailable API.
Derive the entrypoint and mount call from the app's files, keyed off file
extensions rather than the template filenames, which users rename.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(alerts): identify server replica in low-disk alert + per-host dedup tag
The server-mode low-disk alert keyed its dedup tag on the mountpoint alone,
so `simple_alert_helper` mapped every server replica onto a single alert row
per mountpoint. With more than one replica that row flaps every monitor pass:
a replica seeing low disk raises the alert while a replica seeing healthy disk
recovers it. The alert text also could not say which replica tripped.
The fix lives in windmill-ee-private (`low_disk_alerts` in
windmill-common/src/ee.rs) and appends the hostname to both the message and
the dedup tag, mirroring the worker branch.
Also add a regression test pinning the server tag as per-host, and correct the
monitor cadence comments: iterations are LISTEN_NEW_EVENTS_INTERVAL_SEC
(10s by default), not 30s, so "~60s (2 iterations * 30s)" was wrong on both
factors.
* fix(alerts): widen healthchecks.check_type so per-host disk tags fit
Alert tags embed a mountpoint and a hostname, both unbounded, but check_type
was varchar(50). create_alert only logs the insert error while the
notification still fires, so an overflowing tag re-alerts every monitor pass
and never records recovery state.
The server tag overflows for ordinary pod-length hostnames, and the existing
worker tag already overflows for every tracked mount except "/". Widening the
column fixes both; bounding the hostname would not, since the mountpoint alone
can consume the budget.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to b3d01f2c0d2c0714ae95b8a348af22b0fcc30ee4
This commit updates the EE repository reference after PR #666 was merged in windmill-ee-private.
Previous ee-repo-ref: ccd1e42cf6b2d051ca17074fbdf5b80a46cffe0f
New ee-repo-ref: b3d01f2c0d2c0714ae95b8a348af22b0fcc30ee4
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* feat(alerts): include disk total and top consumers in low-disk alert
Point ee-repo-ref at the companion windmill-ee-private commit.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(alerts): pass alert message lazily from the min-version check
simple_alert_helper now takes the error message as a future so callers can
put diagnostic work behind it. Update this call site and point ee-repo-ref
at the companion windmill-ee-private commit.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: point ee-repo-ref at rebased EE branch
Rebase onto EE main so the pin keeps the SAML metadata fixes (394ad23)
that the previous ref carried, and pick up the mount-scoped consumer walk.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: bump ee-repo-ref for bind-mounted file exclusion
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 4d7aafdba33a879b3c60d390c960e57679da9e87
This commit updates the EE repository reference after PR #667 was merged in windmill-ee-private.
Previous ee-repo-ref: 08d3aa4c5bf630d15a28289cca62a0f1da7b7386
New ee-repo-ref: 4d7aafdba33a879b3c60d390c960e57679da9e87
Automated by sync-ee-ref workflow.
* chore: point ee-repo-ref at the merged EE work plus the test fix
ee#667 squash-merged, so the previous pin was a branch commit no longer
reachable from EE main. Point at ee#669, which branches from EE main and
carries the /proc test-portability fix.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to d8a7ac6ae97642a7f4928e6be6846a32dabf4e26
This commit updates the EE repository reference after PR #669 was merged in windmill-ee-private.
Previous ee-repo-ref: 5526aedd73654b9aa4086dae0441b9687ff6415d
New ee-repo-ref: d8a7ac6ae97642a7f4928e6be6846a32dabf4e26
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* fix(frontend): show friendly draft path for draft-only items in pickers
* fix(frontend): dedupe current draft item and scope tab picker by friendly path
* fix(frontend): key live draft picker entries by storage path
* fix(frontend): fall back to the current leaf when the picker highlight key vanishes
* fix(frontend): remount session tab picker when the friendly scope arrives
* fix(frontend): stamp staged tab path for deployed items with undeployed renames
* fix(frontend): expose staged flow/raw-app renames through the live draft registration
* fix(frontend): open session preview picker from whole tab, anchor to tab edge
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(frontend): flatten session preview picker to workspace home level
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(frontend): flatten chat context picker workspace tree
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(frontend): show root loading state in flat drill pickers
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(frontend): make tab-strip keyboard activation work inside dnd zones
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(frontend): return focus to tab after active-tab picker closes
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(frontend): move focus with selection on arrow-key tab navigation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(frontend): display openai reasoning summaries in ai chat with unverified-org fallback
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(frontend): scope hidden-thinking hint per workspace/provider and skip summary on explicit reasoning-off
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(frontend): compose responses fallbacks in either error order and track all unavailable summary keys
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat: add IndexedDB persistence layer for AI-chat artifacts
* feat: add reactive store for AI-chat artifacts
* feat: add artifact chat tools and wire store lifecycle
* feat: add markdown artifact viewer with source toggle
* feat: surface session artifacts in the preview panel and chat list
* feat: tell the copilot when to use artifacts in the session prompt
* test(ai_evals): add artifact case and wire artifact helpers for session context
* fix(copilot): keep in-memory artifacts across same-session resyncs
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: unify session composer edits/artifacts/jobs into a status line
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: add an artifacts section to the session preview picker
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat: share markdown prose presets and restyle the artifact viewer
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor: unify session status popovers into one keyboard-navigable shell
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: reset first-block top margin in all markdown prose presets
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: open the preview picker on the artifacts branch for an active artifact
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: keep artifact picker scope independent of branch hydration state
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Guilhem Lemouel <guilhemlemouel@gmail.com>
The app provenance gate short-circuits on a valid presigned signature, but only the raw download_s3_file route parsed it. The parquet/csv/table-count/file-preview/metadata routes discarded sig/exp and always fell through to the provenance gate, so a presigned S3 object rendered as a table showed "File restricted" for any viewer who did not produce it. Thread sig/exp through every apps_u S3 display route and forward the presigned bearer from ParqetCsvTableRenderer/DisplayResult.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): keep session-exit URL clean by syncing new_draft strip with the router
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(frontend): correct replaceState comment and test-mock wording per review
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(frontend): correct replaceState comment and drop drafting-history phrasing
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): prevent runs timeframe calendar popover overflow on small screens
The Runs page timeframe picker rendered its popover as a wide 3-column row
(preset list + two side-by-side calendars). With the right-aligned trigger and
a center-anchored `bottom` placement, the popup ran off the right edge on
narrow viewports.
Anchor the popover to the right edge (`placement="bottom-end"`) and make its
content reflow to a vertical stack below the `sm` breakpoint, capped at
`max-w-[calc(100vw-2rem)] max-h-[80vh] overflow-auto` so it can never exceed the
viewport. The desktop side-by-side layout is unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(frontend): compact runs timeframe picker with a Start/End toggle on small screens
The two-calendar desktop popover needs ~780px (two min-w-9 grids + presets +
popover padding); below that it overflows. Under 800px, show a single calendar
with a Start/End toggle picking which bound it edits, using set-start/set-end so
each bound keeps its date and HH:MM time inputs — the same precision the desktop
start/end pair offers.
On short/landscape viewports the compact panel is scroll-contained within the
popover's fitViewport height (contentClasses overflow-y-auto, scoped to the
small layout) so its lower controls stay reachable. The desktop two-calendar
layout is unchanged.
Presets are shared between both layouts via a snippet, and the active range is
preserved across the breakpoint since both branches drive the same value.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(frontend): let InlineCalendarInput month/year selects portal, use in compact timeframe picker
Add an opt-in `portalSelects` prop to InlineCalendarInput that portals the
month/year dropdowns to the body (default keeps them in-flow, so existing
consumers are unchanged). The compact runs timeframe picker enables it so the
dropdowns escape its scroll-contained (overflow-y-auto) popover instead of
being clipped.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai): stop sending the AI agent system prompt twice for OpenAI
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai): document collect_system_prompt precedence and trim duplicate comments
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai): hoist only the leading system prompt for OpenAI
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
nsjail caps a jailed job's virtual address space at rlimit_as (4096 MiB for
python3 and ansible). JIT runtimes (Bun/JavaScriptCore, the JVM) reserve large
virtual ranges up front, so a subprocess spawned from a jailed Python/Ansible
job can crash against this cap even when its physical memory use is modest
(e.g. the Bun-compiled claude CLI hitting JSC/pthread allocation failures).
Most other language protos already run with disable_rl: true (unlimited);
python3 and ansible are the outliers with an explicit rlimit_as. This exposes
that cap via a per-language env var (NSJAIL_PY_RLIMIT_AS_MB,
NSJAIL_ANSIBLE_RLIMIT_AS_MB) so operators can raise or lift it on a dedicated
worker pool without a source patch/rebuild and without weakening the
mount/PID/user-namespace isolation that provides the real security boundary.
Only the address-space limit changes; cpu/fsize/nofile rlimits are untouched.
Value is in MiB, or unlimited/none/inf/0 to uncap (rlimit_as_type: INF). Unset
keeps the historical 4096 default.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(apps): invalidate cached app policy on change or deletion (GHSA-r5v4-cxh9-7qhq)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(agents): keep PR tests and comments minimal and non-ephemeral
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The published ghcr.io/windmill-labs/windmill-cli image shipped two fixable
CRITICAL findings:
- openssl (libssl3t64, openssl-provider-legacy): stale in the oven/bun:slim
base image (CVE-2026-34182). Fixed by running apt-get upgrade so the image
picks up the patched Debian packages.
- vitest 2.1.9 (CVE-2026-47429 / GHSA-5xrq-8626-4rwp): a dev-only
devDependency reference in esrap's cached package.json living in bun's
package download cache. The cache is unused at runtime, so it is removed
after install.
Validated by building the image and scanning with Trivy: openssl now reports
3.5.6-1~deb13u2 (fixed) and vitest is entirely absent. wmill still runs. The
only remaining CRITICALs are perl-base CVEs with no upstream fix available.
Fixes GIT-922
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A token scoped only to resources:write:<path> could delete linked secret
variables it had no variables:write scope for, by embedding $var:<victim>
in an attacker-controlled resource value and triggering the resource-delete
cascade. #9712 re-enforced scoped-token boundaries broadly but missed this path.
Add check_linked_var_delete_scopes, called before the cascade in both
delete_resource and delete_resources_bulk: require variables:write for every
linked variable, failing (and rolling back) the delete otherwise. No-op for
unscoped tokens, so full-token cascade cleanup is unchanged.
No co-located-path exemption: a resource and a variable may share a path, and a
resource-write token can create a resource over an existing standalone variable
and self-reference it, so "same path as the deleted resource" is attacker-
forgeable and cannot stand in for variable scope.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai): disable redirects on worker AI provider client (GHSA-5q4v)
The worker AI request path issued provider requests with the shared
HTTP_CLIENT, which follows up to 10 redirects without revalidating each
hop. SSRF validation on the provider base_url is single-shot, so a public
base_url could 3xx the worker into a private/internal host (e.g. cloud
metadata), bypassing the private-endpoint protection. The API proxy was
already hardened in #9370; the worker path was missed.
Add a dedicated AI_HTTP_CLIENT with redirects disabled and use it for the
user-controlled provider endpoint, mirroring the API proxy client.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ai): add ALLOW_AI_BASE_URL_REDIRECTS debug escape hatch
Off by default (redirects stay disabled). When set, restores redirect
following on the AI HTTP client for debugging non-standard/self-hosted
gateways, with a startup warning that it weakens SSRF protection.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(ai): correct redirect comment for the escape hatch override
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(ai): condense redirect invariant comments per review
Anchor the SSRF rationale to ALLOW_AI_BASE_URL_REDIRECTS (the knob that
would break it) and shorten the AI_HTTP_CLIENT and call-site comments to
avoid restating it at multiple sites (AGENTS.md).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(dev-workspace): reflect existing protection rules in lock toggles
When creating or attaching a dev workspace, the "block direct edits" and
"prevent forking" toggles now check the root workspace's current protection
rules. If a restriction is already enforced by an existing rule, its toggle is
shown on but locked, with a note, instead of offering a fresh default that could
misrepresent the effect. The value sent to the backend is derived so it stays
consistent with what the locked toggle shows.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: clarify fail-open comment on dev-workspace lock toggles
Reword the protection-rule fetch comment so the fallback path isn't misread as
dropping protection: a failed fetch falls back to the editable default-on
toggle, and any real rule still enforces server-side.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(dev-workspace): lock protection toggles until rules load
The lock toggles derived alreadyBlocks* from an async fetch, so during the load
window (and the first frame before loading flips) they were editable and the
effective value could be false. A user could turn a lock off and submit before
an existing rule was detected, omitting the reserved rule and silently leaving
prod unprotected once that existing rule was later removed.
Treat "rules not yet known" (loading || current === undefined) the same as
"already enforced": lock the toggle on and keep the effective value true during
that window, so the request can never submit false before the fetch resolves.
Submission stays available (a hung fetch degrades to over-protection, not a
blocked form). Also fixes the stale-value flash when switching base workspace.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(dev-workspace): honor rule bypasses and guard stale protection fetches
Two issues in the protection-rule awareness for the dev-workspace lock toggles:
- Bypassable rules became unconditional locks. alreadyBlocks* used
isRuleActiveInRulesets, which ignores bypass_users/bypass_groups, and forced
the request flag to true. The reserved dev_workspace_lock rule is created with
empty bypass lists, so layering it over an existing rule that let specific
users through revoked their deploy/forking access. Switch to
isRuleUnconditionallyActiveInRulesets so a toggle is only shown as already
enforced (locked) when an existing rule has no bypasses; a bypassable rule
stays editable, making the lock the user's explicit choice.
- A stale protection fetch could apply another base's rules. The generated
client can't take an abort signal, so a delayed response for a previous base
could overwrite the newly selected one. Tag each result with its workspace and
only trust a result matching the current base; also throw AbortError from a
superseded fetch so it can't overwrite current.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: condense protection helper comment to four lines
Trim the isRuleUnconditionallyActiveInRulesets doc comment to satisfy the
AGENTS.md ≤4-line comment rule.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(dev-workspace): align already-enforced note under the toggle label
The note used ml-8, landing under the toggle switch rather than aligned with
the switch edge or the label, so it read as floating. Bump to ml-11 so it lines
up under the label as helper text for that toggle.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
go_on_release.yml referenced this third-party action by the mutable @devel
branch in the step that holds secrets.DENO_PAT (a write-scoped PAT used to push
the generated go-client to another repo). Pinning to a full commit SHA (v1.7.3)
removes the mutable-ref supply-chain exposure, consistent with the other
SHA-pinned actions in the repo.
* feat(nativets): expose standard web-platform globals for bun parity
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(nativets): wire bun-present Event subclasses and add construction smoke test
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(nativets): seed performance.timeOrigin per isolate, drop broken reportError
Addresses CI Codex review on #10112:
- performance.timeOrigin was undefined (setTimeOrigin never called); seed it
per isolate via __wmInitPerIsolate executed from create_nativets_runtime.
- reportError needs a global EventTarget this runtime never installs; drop it.
- reword the namespace-import comment to not describe drafting history.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(nativets): wire DOMException global + broad edge-case smoke sweep
DOMException is present in bun and, more importantly, deno_web references it as
a global: AbortController.abort() with no reason constructs a
DOMException("...", "AbortError"), so the already-wired AbortController/
AbortSignal threw "DOMException is not defined" on abort. Surfaced by a new
functional edge-case sweep (smoke_web_globals_edge_cases) that exercises every
wired global for real (not just presence) — DOMException/abort, AbortSignal.timeout,
EventTarget dispatch, stream tee/reader/writer, all 3 compression formats,
structuredClone Map/Set/Date/circular/reject-function, performance mark/measure,
MessagePort delivery — plus a check that the merged Web Crypto globals still work.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(nativets): restore arg-default smoke tests dropped in merge, drop history comments
Addresses CI Codex/Pi review on the merge commit:
- Merge conflict resolution (checkout --ours) dropped smoke_missing_optional_arg_uses_default
and smoke_explicit_null_arg_is_preserved (added on main by #10111); restore them.
- Reword edge-case-sweep comments to state the constraint, not how the gaps were found.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(nativets): give reportException a global dispatch target; wire stream reader/controller globals
Addresses CI Codex review on #10112:
- P1: a throwing EventTarget listener (and reportError) is routed through
deno_web's reportException, which dispatches on a saved global reference.
With none set, dispatchEvent threw a masking error that hid the original.
Wire a dedicated EventTarget as that target so the ORIGINAL error is reported
(async unhandled, matching bun). Does NOT make globalThis an EventTarget (bun's
isn't either). Re-adds reportError, now functional. Regression test asserts the
original error is surfaced, not a masking one.
- P2: wire the stream reader/controller globals bun also exposes
(ReadableStreamDefaultReader/BYOBReader, ReadableStreamDefault/ByteStreamController,
ReadableStreamBYOBRequest, WritableStreamDefaultWriter/Controller,
TransformStreamDefaultController) for instanceof parity; sweep verifies via real
reader/writer/controller instances.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(nativets): make globalThis an EventTarget so globalThis.reportError() works
Addresses follow-up CI Codex review on #10112:
- P1: the prior fix saved a *separate* EventTarget as the global reference, so
globalThis.reportError() still failed its receiver check (this === globalThis_)
with 'Illegal invocation'. Make globalThis itself the saved reference by turning
it into a functional EventTarget (setPrototypeOf to DedicatedWorkerGlobalScope +
setEventTargetData + webidl brand + saveGlobalThisReference), per isolate in
__wmInitPerIsolate. Both reportError(e) and globalThis.reportError(e) now surface
the original error (async, matching bun) instead of throwing. New test
smoke_report_error_both_call_forms covers both call forms.
- P2: reword the regression-test comment to state the invariant, not the patch history.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(nativets): wire performance constructor globals for bun parity
Addresses the P2 nit in the CI Codex review: bun exposes Performance,
PerformanceEntry, PerformanceMark, and PerformanceMeasure as globals (deno_web
exports all four), so wire them alongside the performance singleton. The
edge-case sweep verifies instanceof against real mark/measure entries.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(nativets): state global-wiring comment as a constraint, not patch history
Addresses the P2 in the CI Codex review: reword the block comment to describe
the current bun-parity constraint and the deliberate EventSource/ImageData
exclusions, without narrating what was or wasn't wired before (per AGENTS.md).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(jseval): raise QuickJS eval memory cap to 128MB with clear OOM error
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(jseval): note bare null/undefined throws are absorbed into OOM bucket
Addresses CI review P2 nit on map_quickjs_error.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(jseval): keep batch-rerun eval on a conservative 32MB cap; tighten OOM match
Addresses CI review: eval_simple_js runs in the API process with unbounded request concurrency, so it must not inherit the raised flow-transform cap. Tighten the Exception OOM match to exact string. Reword drafting-history comments per AGENTS.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(jseval): gate OOM on InternalError kind; path-specific remediation hint
Require the OOM InternalError name (not just the message) so a user throw new Error('out of memory') is not misclassified, and only suggest QUICKJS_MEMORY_LIMIT_MB on the env-tunable flow path (not the fixed-cap eval_simple_js path).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Flow input schemas omit `required` (they carry an `order` key instead),
which made `serde_json::from_str::<SchemaType>` fail in
`convert_schema_to_schema_type`. The error was swallowed and callers fell
back to an empty `SchemaType::default()`, so MCP flow tools advertised no
inputs. Add `#[serde(default)]` to `type`, `properties`, and `required` on
`SchemaType` so these schemas deserialize correctly. Scripts always include
`required` and were unaffected.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* perf(rls): wrap session GUC reads in RLS policies for per-statement InitPlan
RLS policies read current_setting('session.user' / 'session.groups' /
'session.pgroups' / 'session.folders_read' / 'session.folders_write')
directly inside their USING / WITH CHECK predicates. Postgres treats those
unwrapped calls as potentially row-varying and re-evaluates them once per
scanned row, on the read path of every workspace-scoped table.
The GUCs are set with SET LOCAL (set_config(..., true)) in
set_session_context(), so they are constant for the duration of a statement.
Wrapping each session-derived subexpression in a scalar sub-select lets the
planner hoist it to a one-time InitPlan (evaluated once per statement, reused
for every row) — same rows in, same rows out, N per-row GUC lookups collapse
to 1. Array-producing subexpressions keep an explicit ::text[] cast on the
sub-select so `= ANY (...)` / `?|` stay in their array-operand form rather
than being reparsed as a row-returning subquery.
The consolidating migration recreates every existing policy (across ~30 prior
migrations) whose predicate reads a session GUC, by deparsing the current
predicate and substituting the wrapped forms; the down migration is the exact
inverse (byte-identical round-trip). The adding-a-trigger skill documents the
wrapped form so new trigger tables inherit it.
Surfaced by pgrls (PERF001).
Fixes GIT-919
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(adding-a-trigger): fix RLS example cast placement for = any context
The `= any(...)` example put the ::text[] cast inside the sub-select, which
Postgres parses as a row-returning subquery and rejects at CREATE POLICY with
`operator does not exist: text = text[]`. Move the cast outside the sub-select
(matching the migration's canonical form) so the operand stays in array form,
and note why.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The nativets in-process runtime (deno_core) exposed no Web Crypto API:
`crypto` was undefined, so scripts could not use `crypto.getRandomValues`,
`crypto.randomUUID`, or `crypto.subtle`, even though the bun runner provides
them. This closes that parity gap by registering the `deno_crypto` extension
and wiring the crypto globals onto `globalThis`.
- Pin `deno_crypto = "0.223.0"`, the sibling release of the already-pinned
deno_core 0.352 / deno_web 0.240 stack (deps: deno_core ^0.352,
deno_web ^0.240, deno_error =0.6.1), so the rest of the deno stack is
untouched.
- Register `deno_crypto::init(None)` after `deno_web` in both the snapshot
(build.rs) and the runtime (lib.rs) extension lists, keeping the snapshot a
prefix of the runtime list. deno_crypto declares deps = [deno_webidl,
deno_web], which the position satisfies.
- Import `ext:deno_crypto/00_crypto.js` in runtime.js and assign
`crypto` / `Crypto` / `CryptoKey` / `SubtleCrypto` to `globalThis`.
- Add the `smoke_web_crypto` opt-in smoke test asserting the UUIDv4 shape of
`randomUUID`, a non-zero `getRandomValues` fill, and the known
SHA-256("abc") vector via `subtle.digest`.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(mcp): let MCP tokens call preview run tools (jobs:run scope)
The MCP proxy mints an internal JWT scoped to exactly `scope_for_route`
for the endpoint it forwards to. For preview run routes
(`run/preview`, `run/preview_bundle`, `run/preview_flow`,
`run_wait_result/preview`, `run_wait_result/preview_flow`),
`determine_kind_from_route` matched the `SCRIPT_JOBS` prefix
`jobs/run_wait_result/p` (because "preview" starts with "p") and derived
`jobs:run:scripts`. But the preview handlers run arbitrary request-supplied
code with no deployed path and require the broad `jobs:run` scope, so
`jobs:run:scripts` was rejected with 403 "Required scope: jobs:run".
Preview/bundle routes now carry no runnable kind, so the derived scope is
the broad `jobs:run` the handlers expect. This also aligns the route-level
access check with the handler check for these routes.
Fixes GIT-920
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(mcp): anchor preview-route match to endpoint segment
Address CI review: `route_path.contains("preview")` also matched by-path
runs of a deployed runnable whose path contains "preview" (e.g.
`jobs/run_wait_result/p/f/team/preview_report`). Since determine_kind_from_route
also feeds check_route_access, such a route would derive the broad `jobs:run`
and reject a legitimately kind-scoped `jobs:run:scripts:*`/`jobs:run:flows:*`
token with 403.
Anchor the exception to the actual preview endpoints
(`jobs/run/preview*`, `jobs/run_wait_result/preview*`) so by-path runs keep
their kind. Add regression tests for preview-named by-path paths, and trim
the comments per AGENTS.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>