Turning roles on looked for enabled triggers and live captures once,
without a lock anything starting a stream also took. A trigger enabled in
that window could have its listener connect before roles committed, and a
healthy listener never checks again. Both transitions now serialize on one
advisory lock: roles going on hold it exclusive while they look, and
trigger create, edit and enable, and capture setup and ping hold it shared
while they commit. Either the look sees the stream, or the listener
connects after roles are committed and refuses.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Deleting a role reassigns and drops what it owns in each registered
database on its own connection, and each of those passes commits as it
goes. A database failing part-way left the role enabled in the catalog and
able to log in, but already stripped in the databases reached before it.
The role is now disabled in its own commit first, so a failed delete
leaves a disabled role to retry.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: keep pinned import versions of imported scripts in bun lockfiles
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* test: guard pinned imports through an unlocked multi-file bun run
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Creating or editing a trigger that points at a data table under roles was
accepted, and its listener then retried the refused connection every 30
seconds forever. The save is now refused, and a trigger that reaches such a
data table anyway (re-enabled, or cloned into a fork) is disabled by its
listener with the reason, as a missing replication slot is.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A replication stream reads every row of every table whatever the data table's
roles grant, and its listener checks access only when it connects. Rather than
chase every way access can change and bounce the streams each one affects, a
data table now carries one or the other:
- a Postgres trigger or capture cannot be created on, or connect to, a data
table under roles;
- roles cannot be turned on while an enabled trigger or a live capture reads
the data table, its own or a fork's through its pointer. The refusal names
each one to disable.
This removes the stream bounces on roles edits and on data table and workspace
deletion, and the trigger gate that admitted admins. The fork schema baseline
fix from the same review round is kept.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix: let admins and background sync reach private git hosts
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Hb4vHnVrCMqe8ZtNtzFSs
* chore: point ee-repo-ref at the private git host change
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Hb4vHnVrCMqe8ZtNtzFSs
* fix: treat any admin token as admin and pin git probe transports
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Hb4vHnVrCMqe8ZtNtzFSs
* fix: pin git probe transports with a test and say what the caller check skips
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Hb4vHnVrCMqe8ZtNtzFSs
* chore: update ee-repo-ref to eccad9f68bd7246cc81acb82bdb6c08fc6013f45
This commit updates the EE repository reference after PR #791 was merged in windmill-ee-private.
Previous ee-repo-ref: 45ed1331a82dc15e6bdf15fd63517227f9160e21
New ee-repo-ref: eccad9f68bd7246cc81acb82bdb6c08fc6013f45
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* fix: attach TLS to gRPC OTLP exporters for https endpoints
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RbLzVfFDZ9pjBGHSzCSrNZ
* chore: bump ee-repo-ref for the gRPC TLS resolution test
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RbLzVfFDZ9pjBGHSzCSrNZ
* chore: update ee-repo-ref to afd59490e2ca3c375d64cf4a91041763c7de4766
This commit updates the EE repository reference after PR #790 was merged in windmill-ee-private.
Previous ee-repo-ref: b6dd68beb144ecf1b978172396ff8c8bb27ae0c2
New ee-repo-ref: afd59490e2ca3c375d64cf4a91041763c7de4766
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* fix(frontend): keep crawlers off the login page
Every page on the hub links to /user/login with itself in `rd`, so a crawler
sees one login URL per hub page — 4,311 of them in Search Console, all
rendering this same form and flagged as duplicates without a canonical. Nothing
about a login page belongs in an index, on any instance.
Mark the page noindex, as public_run already is, and ship a robots.txt that
keeps crawlers out of /user/ and /api/. The frontend is embedded as static
assets with an index.html fallback, which is why /robots.txt answered with the
app shell until now; a real file in static/ is served as itself.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(frontend): let crawlers fetch the login page so the noindex is seen
robots.txt disallowed /user/, which stopped a crawler fetching /user/login at
all — and a page that is never fetched never shows its noindex. The two halves
cancelled: the URLs would have moved from "duplicate" to "blocked" rather than
out of the index.
Drop the disallow, keeping /api/. And since the app is client-rendered, the
meta tag only exists after a render pass; send X-Robots-Tag on /user/* from
serve_path as well, which a crawler sees on the first fetch.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* fix: surface why a private or untrusted git host is unreachable
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YBriXeDGzjBWjSUTgpkCxW
* test: assert the private git host refusal names ALLOW_LOCAL_GIT_REMOTES
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YBriXeDGzjBWjSUTgpkCxW
* test: pin that the url credential stays out of the refused-host error
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YBriXeDGzjBWjSUTgpkCxW
* chore: update ee-repo-ref to fe2418ff4e5630d6ad3fd85cd2c865bf51c87a2a
This commit updates the EE repository reference after PR #789 was merged in windmill-ee-private.
Previous ee-repo-ref: af0f3ca96f2fbcfa4bf4f8498824c52001d72c55
New ee-repo-ref: fe2418ff4e5630d6ad3fd85cd2c865bf51c87a2a
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
* feat(otel): honor OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013iN8SNZQva5MfMnSM43fHq
* fix(otel): print exporter build failures that tracing would drop
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013iN8SNZQva5MfMnSM43fHq
* chore: update ee-repo-ref to a8ce9bed4b16a01a964d4c35bdc3092b89d10495
This commit updates the EE repository reference after PR #788 was merged in windmill-ee-private.
Previous ee-repo-ref: 7445aa186ca599c3353e041986fe46a277ba9e9f
New ee-repo-ref: a8ce9bed4b16a01a964d4c35bdc3092b89d10495
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
Three fixes from review.
`edit_datatable_config` took `forked_from` wholesale from the stored entry, so
the fork schema diff's save of an advanced baseline was silently discarded and
an applied change was offered again. Whether an entry carries a clone stamp is
still carried from the store, since that is what marks its database droppable,
but the baseline inside it is now taken from the request.
The stranded-pointer warning and the stream bounce ran over the optional
`deleted_datatables` hint, which the settings-sync CLI never sends, so removing
a governing data table through `wmill` bounced nothing. Removals are now derived
from the stored configuration against the saved one.
`delete_workspace` read the pointers to bounce before its transaction, so a fork
committing a pointer during the deletion was missed. The read now happens inside
the transaction, after the workspace row is deleted: a fork's insert key-share
locks that row through its parent foreign key, so it is either seen or fails on
the missing parent.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Deleting a governing data table, or the workspace that holds it, only collected
the fork pointers it stranded, for the warning. A Postgres trigger or capture
already streaming through one of those pointers kept the replication connection
it opened while the pointer still resolved, so it went on dispatching the
governing database's rows after the fork lost access — until its connection
happened to restart. The governing workspace's own streams on a deleted entry
did the same.
Both deletion paths now bounce the affected listeners inside their own
transaction, through the helper a permission change already uses, so a
listener that reconnects re-resolves the entry and finds it gone. The helper is
split so a caller can pass the (workspace, local name) pairs it already holds.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A clone is three requests and `CREATE DATABASE` is not transactional, so a
failure after the first leaves a registered `wm_fork_*` behind, as it did
before data table roles. Accepted for this PR: it is harmless to data and goes
away once the clone is a single server-side operation.
The comment also records why the obvious fix is wrong: reclaiming the leftover
on retry, without durable clone ownership, can drop another workspace's fully
copied database between its import and its final fork request.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This reverts commit 7dd3275a10.
The reclaim tied the caller to the source they administer, but not to the
database it dropped. Between another workspace's import and its final fork
request, that workspace's target is full, registered, unnamed and has no open
connection, so an admin of any instance data table could name it and have it
dropped and recreated empty. The victim's fork would then commit pointing at
the empty copy. Safe reclaim needs durable clone ownership and serialization
with the request that names the database; until then the leftover stays, as it
did before this PR.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A clone creates its target database one request before it copies into it, and
the fork that would name it is written a request after that. Any failure in
between — a pg_dump error, a bad restore, a dropped connection, the source's
roles changing mid-flow — left a registered `wm_fork_*` that no entry names,
and every retry then failed on its name. This predates data table roles.
`create_pg_database` now reclaims such a leftover before creating: only a
`wm_fork_*` database Windmill registered as a data table database and that no
data table or ducklake entry names, in any workspace, archived ones included.
The drop never terminates connections, so a clone still copying into it makes
the reclaim fail instead of being cut off. It is limited to callers who
administer the source — reaching it is not enough, since on a data table
without roles every member reaches it — and anyone else gets the refusal an
existing database always got.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat: show the workspace an operator is in, and let them switch
Operators get a single burger button instead of the developer sidebar, so
nothing on screen named the workspace they were working in.
The button now carries the workspace colour as a disc behind the hamburger,
with the workspace name beside it — permanently on home, and on hover
elsewhere. Workspaces without a colour fall back to a neutral disc so the
button always reads as a control against the page behind it.
A "Switch workspace" submenu lists the operator's workspaces, reusing the
developer sidebar's picker: that list moved out of WorkspaceMenu into
WorkspacePickerBody so both render the same rows. "All workspaces" moved from
the operator menu into the bottom of that picker, outside its scroll area so it
stays reachable.
An operator switching workspaces lands on home, since their page access is
granted per workspace and the page they are on may not be theirs to open in the
one they switch into. Developers keep the existing stay-on-the-page behaviour,
as do pickers embedded in a page that drives its own navigation.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmXFP3eaPPdZuVwVpLuYRX
* fix: decide the operator landing from positive role signals only
`operator_settings` is NULL both for a non-operator and for an operator whose
workspace never had settings written, so a `false` from `isOperatorInWorkspace`
could not stand as proof of a developer. Lead with `userStore.operator`, an
explicit flag for the workspace being left, and keep the target check only as a
confirmation. Also states the picker's expansion-seeding invariant as what the
host menu actually guarantees: a close and re-open inside its 100ms outro
resumes the same instance rather than a fresh one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmXFP3eaPPdZuVwVpLuYRX
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Selecting a step in the flow editor sometimes opened the Settings panel
instead of the step that was clicked.
SelectionManager.selectId() clears xyflow's own selection before setting
ours, and that clear was wired to clearFlowSelection(), which resets
offsetNodeCache and reassigns nodes. That hands xyflow node objects it
does not recognise, which is what makes it drop the flag — but it also
makes it re-create every node's DOM: 126 elements on a 25-node flow, on
every selection.
Graph nodes select on pointerdown. A click is dispatched on the closest
common ancestor of its pointerdown and pointerup targets, so when the
release lands in that teardown gap the browser hit-tests to the pane, the
click addresses the pane, and onpaneclick clears the selection back to the
settings sentinel.
Clear through store.unselectNodesAndEdges() instead, so no node object
changes identity and there is no gap to fall into. clearFlowSelection
keeps its two group-creation callers, which rebuild the graph anyway.
Claude-Session: https://claude.ai/code/session_01R7caEKonPr2DmvsWRR6bCK
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
A clone is three stages a workspace apart — `create_pg_database`, then
`import_pg_database`, then `apply_forked_datatable` inside the fork transaction.
Only the third can roll back, and `CREATE DATABASE` is not transactional, so any
refusal that lives there strands a registered `wm_fork_*` that no entry names
and whose name blocks the retry.
That orphan has now been fixed three times, most recently reintroduced by a
guard added one commit ago. Patching each new refusal into the first endpoint is
not the fix; having two places that can refuse is. `ensure_datatable_is_clonable`
now answers every reason a copy can be refused and returns what it resolved, and
the stage that writes the entry only does the work.
Also takes an ACCESS EXCLUSIVE lock before the rollback guard counts, so a role
created concurrently cannot slip between the check and the drop.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BjfMkJyKzodxkobqGZ6Lqb
The down migration dropped the table and left every role behind: live Postgres
logins whose passwords only that table carried, so after a revert Windmill could
neither use, disable nor delete them, and re-applying could not recreate them
because the names were taken. Cleaning up here is not possible either — dropping
a role means reassigning what it owns in every instance database, and a
migration runs in one — so it now refuses while the catalog is non-empty and
says to delete the roles through instance settings, which does the cluster work.
Also enforces the instance-only invariant the resolved-pointer clone relies on
rather than only asserting it in a comment.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BjfMkJyKzodxkobqGZ6Lqb
Forking a fork with cloning left an orphan database. The preflight resolves the
pointer and sees the governing entry, so both endpoints ran and filled the new
database; `apply_forked_datatable` then refused the inherited pointer and rolled
the fork back, stranding a registered `wm_fork_*` that no entry names and whose
name blocks the retry.
Refusing earlier would have been the smaller change, but forking a fork and
cloning worked before pointers existed, so it would trade an orphan for a
regression. Resolve what the pointer names and write the terminal entry the
clone needs: the whole `database` object rather than a patch of its
`resource_path`, since a pointer has none, and `reference` removed with it.
Also accepts `-- role=x` and `-- Role = x`, two more spellings that fell through
to the default role.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BjfMkJyKzodxkobqGZ6Lqb
* fix(frontend): restore heading sizes in note markdown and keep group notes on id change
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0182GroR5mvi9ksRTMmBT7nE
* fix(frontend): scope the note cleanup deferral per note
Address local review nits: the header comment overstated the heading ramp (h3
sits at body size in the xs and sm scales), and the mid-update deferral bailed
out of cleanup for every group note rather than the one holding the unrendered
module.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0182GroR5mvi9ksRTMmBT7nE
* perf(frontend): traverse modules without collecting a discarded array
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0182GroR5mvi9ksRTMmBT7nE
* test(frontend): show the three markdown prose presets in the kitchen sink
The Markdown tab rendered only the default preset, so a change to the shared
heading scale could not be compared across the surfaces that use it. The chat
sample gains headings for the same reason: it is the only place the assistant
bubble renders at panel width without an AI provider.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0182GroR5mvi9ksRTMmBT7nE
* refactor(frontend): validate group notes against the flow, not the render
A group note's members are module ids, so the flow's own module list decides
whether one still exists. Validating against the rendered nodes instead needed
a special case for collapsed groups, and still dropped a live module in the
pass after its id changed. Both cases are the same mistake, and checking the
source of truth removes them together with the collapsedModuleIds parameter.
Path completion keeps working off the rendered graph, since it needs the edges,
and now skips a note whose members it cannot all see rather than dropping them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0182GroR5mvi9ksRTMmBT7nE
* fix(frontend): name a new note "Note", not after its internal type
The prefill lands inside the user's own note, and "Free note" / "Group note"
are the serialized type, not words anyone says about a note they just drew.
The menus that create them already say "Add note".
The heading goes to h2 as well: h3 computes to the body size in the note scale,
so the prefill's own title did not read as one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0182GroR5mvi9ksRTMmBT7nE
* docs(frontend): describe what collapsedModuleIds is still for
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0182GroR5mvi9ksRTMmBT7nE
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two review findings, both real.
`attach_datatable_parses_name_and_role` never compiled: `parse_attach_datatable`
returns `Result<Option<_>>` now and one call site kept a single `unwrap`. Its
`?Role=analytics` case also asserted a refusal, contradicting the parser in the
same commit, which matches the key case-insensitively. Replaced with the cases
that are genuinely malformed, and a positive one for the cased key.
`-- role: analytics` fell through to the default role — the silent fallback the
strict parser exists to remove, for the spelling most likely to be typed. The
keyword now accepts an optional colon, attached or spaced, while a word that
merely starts with it (`rolebased`) is still not an attempt.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BjfMkJyKzodxkobqGZ6Lqb
* fix(otel): pick up standard OTEL_RESOURCE_ATTRIBUTES on the exported resource
The OTEL resource was built with `Resource::builder_empty()`, which runs no
resource detectors, so attributes injected through the standard
`OTEL_RESOURCE_ATTRIBUTES` env var were silently dropped. Deployments that
inject `k8s.pod.uid`, `k8s.container.name` or `service.namespace` saw none of
them reach their backend.
Use `Resource::builder()`, which seeds from the SDK's env detector. Windmill's
own attributes keep being applied on top, so per the OTel resource spec the env
var is the secondary resource and `service.name`, `service.version`,
`host.name` and `deployment.environment*` stay authoritative.
The EE change lives in windmill-ee-private; this carries the ee-repo-ref bump
and a regression test pinning both halves of the contract.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yUJaBDHPgjZPodP2u4aqR
* test(otel): clear OTEL_HOST_NAME so the resource test is hermetic
OTEL_HOST_NAME takes precedence over the hostname argument, so an ambient one
failed the host.name assertion with a message pointing at the merge logic.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yUJaBDHPgjZPodP2u4aqR
* feat(otel): honor OTEL_SERVICE_NAME and OTEL_SERVICE_VERSION
Deployments identify each pod from its own labels, e.g. through the Kubernetes
downward API, so `service.name` and `service.version` must be settable per pod.
Both were ignored: OTEL_SERVICE_NAME was read by the SDK and then overwritten,
and because the two attributes are set in code they also outrank
OTEL_RESOURCE_ATTRIBUTES, leaving no route to set them at all.
The EE change lives in windmill-ee-private; this carries the ee-repo-ref bump
and tests for the dedicated overrides.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yUJaBDHPgjZPodP2u4aqR
* fix(otel): keep service.version pinned to the build version
OTEL_SERVICE_VERSION is not an OTel env var, and service.version identifies the
build that produced the telemetry, which a deployment cannot state more
precisely than GIT_VERSION already does. A deployment that wants its own release
version in telemetry can carry it under its own key.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yUJaBDHPgjZPodP2u4aqR
* test(otel): pin OTEL_SERVICE_NAME above service.name in OTEL_RESOURCE_ATTRIBUTES
The spec ranks OTEL_SERVICE_NAME above a service.name carried in
OTEL_RESOURCE_ATTRIBUTES; that ordering was only checked by hand. The three
candidate values are distinct, so the assertions fail if either ranking breaks.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yUJaBDHPgjZPodP2u4aqR
* feat(otel): honor OTEL_SERVICE_VERSION
The spec defines no OTEL_SERVICE_VERSION, but deployments set it expecting it to
work because it sits next to OTEL_SERVICE_NAME, and setting service.version in
code blocks the OTEL_RESOURCE_ATTRIBUTES route, so there is otherwise no way to
set it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yUJaBDHPgjZPodP2u4aqR
* test(otel): guard against unknown_service on a default deployment
Resource::builder seeds SdkProvidedResourceDetector, which sets service.name to
"unknown_service" when neither OTEL_SERVICE_NAME nor a service.name in
OTEL_RESOURCE_ATTRIBUTES is present. Only our own attribute keeps that out of
the exported resource, and no assertion covered the case where nothing is set
at all — which is the default deployment.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yUJaBDHPgjZPodP2u4aqR
* test(otel): pin the empty-means-unset fallback for OTEL_SERVICE_VERSION
The empty case asserted the fallback for service.name and host.name but not
service.version, leaving one branch of the three-variable contract uncovered.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yUJaBDHPgjZPodP2u4aqR
* chore: update ee-repo-ref to b964f0caaae57dc526c7ac9dc54d753372989f63
This commit updates the EE repository reference after PR #779 was merged in windmill-ee-private.
Previous ee-repo-ref: 62efa909aabdba4cb31ffabe9aae0e4909ca1e07
New ee-repo-ref: b964f0caaae57dc526c7ac9dc54d753372989f63
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
Enforcement ships first. The permissions drawer is what turns roles on, and the
catalog section is what creates them — both are only useful once there is a way
to grant a role the privileges it needs, which arrives with the ACL editor. Left
mounted they would offer a feature whose other half does not exist.
The two components are complete and reviewed; only their call sites here are
commented out, with a note pointing the follow-up PRs at them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BjfMkJyKzodxkobqGZ6Lqb
The retry rebuilds its SQL from `pruneComments(code)`, so the leading comment
block never reached the second attempt — and with it the `-- role <name>` line
that decides which login the query runs as. The retry connected as the data
table's default role instead, so a query the first attempt was denied could
succeed on the second, reported as "recovered with the row_to_json fix".
Carry the leading comment block over. The retry itself is unchanged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BjfMkJyKzodxkobqGZ6Lqb
`?Role=analytics`, `?role=` and `?x=1&role=…` all fell through the reference
parser's exact-match rule, so the connection resolved to the data table's default
role and ran under a login the caller never asked for — the URI half of the same
trap as a malformed `-- role` annotation.
The key now matches case-insensitively, and anything else in the query string is
an error naming it; `role` is the only parameter a reference takes. Callers that
only need the entry keep a lenient `datatable_ref_name`, since they never act on
the role. The DuckDB `ATTACH` parser propagates it rather than attaching under
the default.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR
`-- Role operator`, `-- role operator;` and `-- role operator -- why` all failed
the annotation parser's exact-match rule, so the query fell through to the data
table's default role and ran, silently, under a login the author did not choose.
Naming a role exists precisely to not do that.
A leading comment whose first word is `role` is now an annotation attempt: the
keyword matches case-insensitively, one trailing `;` is tolerated, and anything
else is an error naming the line. Only callers that already know the target is a
`datatable://` reference ever run this, so ordinary SQL keeps its comments.
Also bumps the dev shell's postgres client to 18 — it trailed the server the dev
database runs, which takes out every data table export, clone and fork-with-data.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR