Commit Graph
7188 Commits
Author SHA1 Message Date
Diego Imbert dfc06bf821 test(datatables): pin that freeing a principal takes its tenant with it
The cleanup is one helper called from five routes, and the bug each time was a
route that did not call it. This drives four of them — a group deletion, a
folder deletion, a member leaving, a superadmin deleting the account — against
a real API server and asserts the role stops naming them, while the `*`
wildcard, which no deletion can free, is left alone.
2026-09-03 19:50:59 +02:00
Diego Imbert 2c55c3918c fix(datatables): every route that frees a username takes its tenants with it
Three deletions bypassed the workspace-user cleanup and left `u/<username>` on
the roles it named: the superadmin's global delete, which frees the name in
every workspace at once, and both leave routes. A member invited into one of
those names afterwards inherited the roles.

The settings lock says what it hands back — the config as stored, generated
role passwords included — so it carries `_unchecked` and the contract that goes
with it: callers authorize the read, and redact before passing the value on.
2026-09-03 19:43:39 +02:00
Diego Imbert 625753c2d4 fix(datatables): one lock for everything that reads the config and writes it back
The role save took an advisory lock of its own while principal cleanup took the
settings row, so the two never excluded each other: a save could persist a
permissions block it had computed before a group's deletion took that group off
it. The settings form had the same shape with no lock at all — it carries the
old permissions forward by construction.

They all take the settings row now, before reading and until they have written,
which is the lock the cleanups already used. One mechanism, so there is no
ordering to get wrong, and the advisory lock goes away with its cached query.

The tenant removal says what it does not do: it authorizes nothing, and the
rules differ per caller — a workspace admin for a user, the owner for a group
or folder, no identity for the system paths — so the name carries `_unchecked`
the way the resolution helpers next to it do.
2026-09-03 12:17:26 +02:00
Diego Imbert 0fc9ba39a7 fix(datatables): a tenant and a resource path follow the principal they name
A data table role names its tenants — `u/alice`, `g/devs`, `f/team` — and a
data table names its database by resource path. Both are strings that outlive
what they point at, and the flows that free those names left them behind:

- Deleting a workspace user, a group or a folder left its tenant on every role
  it could run as. The name is free afterwards, so whoever takes it next
  inherits the role without an admin granting it. All three now drop it in the
  same transaction that removes the principal.
- Offboarding reassigns a user's resources to a folder without the data table
  following, which both stops it resolving and frees the path it named — a new
  resource there points it at another database with its roles intact. The
  username rename had the same gap. Both move the config with the resource.

The walk both need is one function each now, with the table test that pins
which tenant forms it touches.

Saving permissions also takes a lock per data table, held from the read that
plans to the write that persists: two saves interleaved each planned against
what the other was leaving, and the one that persisted last stored roles the
other had already dropped. The ACL apply takes the same lock, since it reads
the same config and catalog.
2026-09-02 10:27:28 +02:00
Diego Imbert 83acf4ba56 fix(datatables): follow the resource through variables, and a tenant through a rename
A linked variable owns the resource at its path: deleting one — singly or in
bulk — deletes it, and renaming one moves it, so all three answer to the rule
the resource endpoints do.

And a data table role's tenants are stored as u/<username>, which the executor
compares against the caller's name. A username rename that skipped them took
the role away from the user it followed and left it waiting for whoever took
the old name next.

Also: the ACL endpoints stopped saying 'admins only' — a non-admin may act on
what their role owns, which is the contract the handlers implement.
2026-09-02 00:39:12 +02:00
Diego Imbert 65401f39fe fix(datatables): a resource cannot be taken out from under a permissioned data table
Comparing identities only answered for an edit that kept the resource. Clearing
its value, deleting it, renaming it away or bulk-deleting it all left the config
naming a database its roles were never created in — and the next resource at
that path would answer for grants it never had. Every one of those refuses now
while permissions are enabled, and the same disable-first escape stays.
2026-09-02 00:18:24 +02:00
Diego Imbert 2cde025d8e fix(datatables): guard the resource upsert too, and cache its query
create_resource with update_if_exists is an edit when the row is already
there, so it answered to none of the rule the other two write paths do. And
the lookup the guard makes had no offline entry, which is what CI compiles
against.
2026-09-02 00:00:07 +02:00
Diego Imbert c289a40e5b fix(datatables): keep role passwords out of the settings every member reads
`get_public_settings` is the one the logged-in layout calls for every user, and
it served the data table config as stored — generated role logins included. A
member who is a tenant of no role could read every role's password and connect
as it directly, which is the whole tenant model. It goes through the same
redaction as the admin settings and the tarball now.

Also, on the way there:

- The permissions drawer opened from a data table's row sent the role the
  manager is connected as, which belongs to another data table: roles are
  per data table, so a row under another one is read as its own default role.
- The grant repair on an instance database ran before `apply_datatable_acl`
  authorized anything, so any member could drive that privileged connection
  with a request about to be refused.
- That repair also assumed schema `public` exists, and failed whole where it
  had been dropped.
2026-09-01 23:52:05 +02:00
Diego Imbert 68e7f72560 fix(datatables): the database a permissioned data table points at cannot move
The path in the config staying the same said nothing: a postgres resource is
editable in place, so its host, database or user could change underneath roles
whose logins and grants live in the database it used to name. The identity a
connection resolves to is what has to hold still while those roles exist; a
password rotation is not an identity change and stays allowed.

Also generalizes the admin guard: what 'admin' holds is what every role here
connects through, on the database and on schema public alike, so a revoke
naming it is refused wherever it is aimed.
2026-09-01 23:37:05 +02:00
Diego Imbert 98adfd247b chore(ee): point at the rename-message fix 2026-09-01 23:23:32 +02:00
Diego Imbert 88d6e11ff0 fix(datatables): a permissioned data table cannot be pointed at another database
Its roles live in the database it points at: the logins were created there and
every grant they hold is recorded there. Carried onto another database they
authenticate against a cluster that never heard of those grants. Opting out
first is what drops them from the database they belong to.

Also: the ATTACH test now calls the parser the executor runs, rather than a
byte-identical copy of its regex that no regression could reach.
2026-09-01 23:19:42 +02:00
Diego Imbert 6e79d9636f fix(datatables): keep the database's own grants to admin out of reach
admin is the login the data table reaches Postgres through, so revoking on
the database itself takes away what every role here connects with — and what
the role that would grant it back connects with. Refuse it server-side, and
stop the drawer from offering a row it cannot act on.
2026-09-01 23:15:43 +02:00
Diego Imbert be186e6fdd fix(datatables): the objects a revoke names do not answer for its scope
The ownership guard took the named-objects branch whatever the scope, and
returned. A revoke naming one table the caller owns, scoped to all tables,
therefore passed a check over that one table and planned a statement that
names the whole schema — the objects never reach the SQL there.
2026-09-01 23:10:53 +02:00
Diego Imbert d6f36d3135 test(datatables): pin the object classes a schema-wide change reaches 2026-09-01 23:05:00 +02:00
Diego Imbert 9a63c6884d fix(datatables): count materialized views among the tables a scope reaches 2026-09-01 23:02:54 +02:00
Diego Imbert 6768e2a9e9 fix(datatables): check what a change reaches, not only what it names
Owning the target is not owning what a change through it covers: a scope that
reads IN SCHEMA names every object in the schema, and handing a schema over
takes them all with it. Postgres would have skipped the ones the caller does
not own — these statements run as the data table's admin, so it will not.
Refuse, naming the object that is not theirs, and let a workspace admin
through as before.

A default-privilege rule speaks for the role that creates the objects, so a
non-admin now only writes them for the roles they may run as.

Also: the revoke button follows can_manage like the grant builder already did,
the copy path resolves a data table as admin for an admin (dumping as a
restricted role silently omits what it cannot read), and two doc comments now
sit on the function they describe.
2026-09-01 23:00:51 +02:00
Diego Imbert fa4c06658f fix(datatables): resolve ACL objects against the catalog, and gate on the enterprise edition
A revoke's objects came from the request, argument types included, and those go
into the statement unquoted — so a schema owner could close a routine signature
and append SQL that ran as the data table's administrative login. The request
now only names an object: what reaches the statement is read back from the
catalog, and an object that resolves to nothing is refused.

The planners were behind `private`, which community builds carry, so the
permissions API answered on a CE binary. They take `enterprise` as well, with
a test that pins the refusal in every other edition.

Default privileges are read back scoped to one data table's own roles: two data
tables can share a database, and a new role of one was inheriting the other's
rules.
2026-09-01 11:58:58 +02:00
Diego Imbert 45d5eeda0e fix(datatables): close the fail-open in role resolution, and gate the ACL endpoints
A role with a stored pg_rolename but no password resolved to the data table's
own connection, which owns everything — so a caller authorized as one role got
the admin one instead. Exports and git-synced settings redact that password, so
a restored config is exactly the shape that produced it. Refuse instead, and
name the fix.

The ACL endpoints took any workspace member: on a data table without roles
every member resolves to that same admin connection, so ownership and grants
there are the workspace admins' to change, as the roles themselves are.

Also: check a migration batch's roles before applying any of it, keep the role
picker for a single non-default role, drop the revoke button from a default
privilege on types (which no scope can express), and say what
get_datatable_resource_as_default_role actually resolves as.
2026-09-01 04:48:44 +02:00
Diego Imbert 28b31a437e feat(datatables): gate role and ACL changes on the enterprise edition
Both planners now live in windmill-ee-private, reached through a dispatcher
that refuses in the open-source build, and an enterprise binary additionally
checks for an active license before planning anything. Reading a data table's
roles, its usable roles and a schema's owner and grants stays open, so an
instance that lapses can still see what it has; every mutation is refused.

The UI stops offering what the server would refuse: no Roles entry on a data
table, and no Permissions entry on a schema or table, without a license.
2026-09-01 04:32:52 +02:00
Diego Imbert 5f59b199b1 test(datatables): pin the audit parameter's redaction 2026-09-01 04:14:29 +02:00
Diego Imbert 2bc39481da fix(datatables): cover routines in ownership, grants and default privileges
Functions live in pg_proc, so a schema changing hands left them behind and a
grant on ALL FUNCTIONS disappeared from the drawer on the next read, with no
way to revoke it. Read them alongside pg_class, and name them by their
identity arguments, which is what tells two of one name apart.

ALTER DEFAULT PRIVILEGES binds only the roles it names, so a role created
after a 'created later' grant produced tables no one else could read.
Replay the rules already in force for each new role.
2026-09-01 04:09:19 +02:00
Diego Imbert 7619a7a51e fix(datatables): keep role passwords out of audit, and role names out of SQL
The audit parameter of a data table config save carried the whole settings
blob, generated role passwords included. Redact it the way every other
export of that blob already is.

Both SDKs pasted the caller's role straight into the `-- role` annotation,
where a newline ends the comment and leaves the rest running as whatever the
first line named. Check the value against the role-name grammar the server
enforces.
2026-09-01 04:09:12 +02:00
Diego Imbert a4e6eb5e4d fix(datatables): never hide access management from a workspace admin 2026-09-01 03:11:23 +02:00
Diego Imbert e3f0a22810 feat(datatables): manage access on what you own, not only as an admin 2026-08-31 23:39:49 +02:00
Diego Imbert 22f99f99f1 fix(datatables): grant created roles to admin with the SET option 2026-08-31 23:39:49 +02:00
Diego Imbert 709cf8aecb Merge remote-tracking branch 'origin/main' into datatable-perms-3
# Conflicts:
#	backend/ee-repo-ref.txt
2026-08-31 22:01:47 +02:00
Ruben Fiszelandrubenfiszel 412eb90c0d chore(main): release 1.800.0 (#10888)
* chore(main): release 1.800.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2026-08-31 20:29:23 +02:00
716ce2ece0 feat: free AI tokens + home search/filter revamp (#10020)
* feat: add free Claude Opus tier with per-user token limit

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* nit move alert

* Home AI Chat

* wire home ai chat

* auto send prompt

* refactor: remove keyboard arrow-navigation from home list

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat: replace home search bar with unified FilterSearchbar

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat: replace home quick tags with FilterSearchbar presets

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat: add content filter to home FilterSearchbar with EE-gated content view

- Clear the kind filter by deleting the key (was showing a 'kind: null' tag on All)
- Remove the standalone Content button
- Add a 'content' filter; when set, render the Ctrl-K content-search view
  (ContentSearchInner) which shows text-match snippets and its own EE warning

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat: disable home AI chat and prompt to configure AI when no model

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* track cost instead of tokens

* nit

* fix: load copilot config on home so AI chat isn't wrongly gated

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Home page update

* nits

* example prompts

* nit

* feat: switch free AI tier to DeepSeek with daily cost budgets

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* nit

* Move bottom buttons to HomeAIChat

* [ee] feat: surface free AI tier state and make its metering abort-proof

Makes the free Windmill AI tier legible to the user and closes an abuse hole.

Backend:
- AIConfig gains a response-only free_tier marker (skip_deserializing so a
  client can't store a forged one via edit_copilot_config). get_copilot_info
  keeps returning it once the grant is spent, so the client knows AI is off
  because the grant ran out, not because nothing was configured.
- Per-user grant becomes one-time (migration drops the day key from
  ai_free_token_usage); the daily table stays as the instance kill-switch.
- Reserve-then-reconcile metering (see EE commit) so a mid-stream disconnect
  can no longer dodge the usage report and get metered zero.

Frontend:
- copilotInfo carries freeTier; model settings show a "Free" pill and a
  usage meter that warns past 80%.
- The home chat and the session chat show a dedicated "you've used your free
  Windmill AI, add your own API key" state instead of the generic
  "no provider configured" one.
- A failed send re-fetches copilot_info so the exhausted state (and its
  banner) appears live, without a page reload.

Bumps ee-repo-ref.txt to the matching EE commit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat: free AI usage meter reusing the context-usage gauge

Show free-tier spend with the same gauge as context usage instead of a
bespoke block:

- Extract the meter+tooltip into a shared UsageMeter; ContextUsageIndicator
  uses it, and a new FreeTierUsageIndicator renders it from
  copilotInfo.freeTier. Placed in the session-chat toolbar and next to the
  home-chat model settings; the old meter block in the model-settings
  dropdown is removed (the "Free" pill stays).
- Hide the context-usage bar while on the free tier so the free meter takes
  that slot.
- Refresh copilotInfo after every free-tier turn (AIChatManager finally) so
  the meter advances live and the turn that exhausts the grant flips to the
  exhausted state, instead of both only updating on reload. Gated to active
  free-tier users, so it costs nothing for configured-key users.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: fix stale free-tier comments after DeepSeek/cost rework

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat: always show context bar, replace free-tier meter with usage banner

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* nit

* fix: atomic free-tier budget reservation (ee ref + sqlx)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: keep CLI/MCP and Hub buttons unblurred on AI chat hover

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Add back arrow nav

* nit

* nit

* fix: three review P1s in the home AI chat & search

- AIChatManager: refreshFreeTierUsage now bails unless the global copilot
  state still belongs to the completing manager's workspace, so a warm
  session finishing after a workspace switch can't reload its (background)
  workspace over the active one's models/client/copilotWorkspace.
- HomeAIChat: block submission until the copilot config is loaded AND
  enabled (new `canSend`), so a prompt submitted during the unknown-config
  window isn't handed to a session that never sends it and silently lost.
  The disabled overlay still gates on config-loaded to avoid a flash.
- ItemsList: the content-search reload effect now depends on $workspaceStore
  so content results follow the active workspace instead of showing the
  previous one's.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* [ee] fix: harden the three home-AI-chat/search P1s after deeper review

Follow-up to the previous P1 commit; sharper review found the earlier guards
insufficient:

- refreshFreeTierUsage now compares against the most-recently-*requested*
  workspace (new copilotWorkspaceRequested in aiStore, set synchronously in
  loadCopilot), not the last-*resolved* one — otherwise a warm session
  finishing while a newer workspace's load is still in flight could win the
  monotonic token and restore its stale workspace over the one being loaded.
- The content-search view is keyed by workspace ({#key $workspaceStore}) so a
  switch remounts ContentSearchInner; late in-flight responses from the
  previous workspace can no longer land in the new one's component.

Backend (EE, via ee-repo-ref bump to 03ef0eb): the free-tier reservation now
also prices the worst-case input cap (at the cache-miss rate), and
enforce_free_tier_body rejects oversized prompts and pins n=1 — so an aborted
large-prompt request can no longer dodge the input bill that reconciliation
would otherwise charge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: exclude service accounts from the free AI tier

Free-tier eligibility was keyed solely on authed.email. Workspace admins can
create and impersonate arbitrary service accounts (synthetic *.sa.wm.dev
identities), each of which would receive its own one-time grant — letting one
tenant mint many grants and drain the instance-wide daily allowance. Skip the
free-tier fallback for *.sa.wm.dev identities.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: activate free AI tier when clearing a workspace provider

edit_copilot_config returned AIConfig::default() when the saved workspace
config had no providers and no instance config existed; the frontend applies
that response immediately, disabling AI even though the free-tier key is
available. A later get_copilot_info (on reload) returns the synthetic free-tier
config, so clearing a provider behaved inconsistently until reload. Give this
response path the same free-tier fallback as get_copilot_info.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: gate the home AI composer behind the global-AI dev flag

The "Build with AI" composer starts a session and navigates to /sessions, which
lives behind the same wm_dev_global_ai dev gate as the global AI chat. With the
gate off (the default), /sessions renders only its gate message, SessionWrapper
never mounts, and the queued prompt is silently dropped. Hide the home entry
point behind isGlobalAiEnabled() so it isn't exposed before the sessions gate
opens.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* [ee] chore: bump ee-repo-ref for deepseek-v4-flash price/model fix

Points at the EE commit that pins deepseek-v4-flash and its real prices
(pico-precision accounting).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* [ee] fix: provable byte bound for the free-tier input cap (ee-repo-ref)

Bumps ee-repo-ref to the EE commit that caps the raw request body byte length
directly (token_count <= byte_count is provable), replacing the unsafe
body.len()/2 token estimate that high-entropy prompts could beat.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* nit isGlobalAiEnabled

* empty commit

* fix(frontend): address Codex review on free-tier / home filters

- P1: home filters now sync from the URL reactively, so browser Back/Forward
  updates the chips, kind toggle and results (and clears keys dropped from the
  URL) instead of leaving them stale until the next filter edit.
- Free-tier banner buttons drop deprecated Button props (size/color/border
  variant) for unifiedSize + a supported variant.
- Condense refreshFreeTierUsage comments to a single race-condition constraint
  beside the guard.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(frontend): hide empty kind badge on draft-only scripts

A draft-only script can carry an empty `kind`, which still isn't 'script' so the
row rendered a blue badge whose only content was capitalize('') — an empty pill
left of the "Draft only" badge. Guard the badge on a non-empty kind.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(frontend): animate home tree-view group expand/collapse

Wrap each owner group's children in ResizeTransitionWrapper so height changes
animate. A slide transition only animates the initial mount, but a freshly-opened
owner fetches its rows and passes through a transient empty state before they land
— the ResizeObserver animates that second growth too. Nested TreeViews inherit the
wrapper's context and skip their own, so one observer per top-level owner animates
the whole subtree.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(frontend): FilterSearchbar boolean auto-set and string-filter presets

- A default-false boolean filter has only one useful value, so selecting it sets
  true immediately instead of opening a true/false picker. A default-true boolean
  (e.g. "Include library scripts") still shows the picker, where false is the
  meaningful choice — expressed via a new optional `default` on the schema.
- A plain string filter now surfaces any presets targeting it (`<tag>:<value>`)
  as suggestions once selected, integrated into menuItems so keyboard nav works —
  previously selecting e.g. "Owner" showed nothing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(frontend): home page toolbar and content-filter revamp

- "New" create-menu button (scripts/flows/apps/…) replaces the old Content button;
  the search bar moves to the right of the toggle group.
- Restore the content filter dropped in a merge: a `content` searchbar filter swaps
  the list for the full-text ContentSearchInner view (EE), aligned flush with -mx-2.
- Move the owner/group and label chips off the page into FilterSearchbar presets;
  ownerFilter/labelFilter now derive from the searchbar keys (data layer unchanged).
- Move the list controls (select / tree view / expand-all / sort) inline into the
  top row between the toggle group and search bar; add margin above the list.
- Beta tag on the home AI chat; a bit more bottom margin under it; tighten the gap
  between the admin/tutorial banners and the list.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(ai): pass the request body to the free-tier reservation

Thread the prompt body into resolve_free_tier_credentials so the free tier can size its
upfront reservation from the actual request length instead of a fixed worst case (EE
c2e248b), fixing normal chats being rejected as "too large". Updates the OSS stub signature
and bumps ee-repo-ref.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(frontend): gate home Create/Import menu on edit permissions

The relocated CreateActionsMenu rendered unconditionally, so operators and users in
workspaces protected from direct deployment saw create/import actions they can't use.
Restore the original gate (!operator && showEditButtons, the latter from NoDirectDeployAlert).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(frontend): address Codex review on filter searchbar

- P1: the boolean shortcut now goes through the same tag-insertion path as the normal
  branch, so it removes the typed search segment instead of leaving it as a stray
  free-text (_default_) term.
- Mark the Runs `show_future_jobs` filter default: true so selecting it opens the picker
  (false is the meaningful choice) rather than being a no-op.
- Home owner/label presets now emit the canonical `key:\ value` form so the applied-preset
  check matches after a reparse and can't re-offer a duplicate; update the suggestion
  extraction to strip the leading separator.
- Replace deprecated Button props (size/spacingSize/color) on the relocated list controls
  with unifiedSize.
- Fix stale comments: UsageMeter no longer claims a free-tier consumer; the home filter
  schema comment describes presets, not the removed ListFilters/label badges.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(frontend): boolean filter shortcut sets value canonically

The round-1 shortcut baked `true` into the tag text, which merged into a following tag
(e.g. `archived:\ truekind:\ flow`). Instead remove the typed segment, set the value, and
reparse so the text is rebuilt canonically — no lingering free-text and no merge.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(ai): restate free-tier caller identity contract in the OSS stub; bump ee-repo-ref

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(frontend): keep flanking tags separate when boolean shortcut drops a segment

Joining `before`/`after` directly fused the tags a removed mid-segment sat between
(e.g. `kind:\ flowsummary:\ bar`). Join with a space; reparse then canonicalizes. Also
trims the comment to the essential constraint.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(ai): update sqlx cache for free-tier daily-day queries; bump ee-repo-ref

The reserve/reconcile daily-usage queries now bind the reservation day (EE change); refresh
their offline query cache and point ee-repo-ref at the EE commit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(ai): activate free tier when instance ai_config has no provider

An instance ai_config row won precedence just by existing, so an empty {} (valid via global
settings / declarative config) suppressed the free-tier fallback and left AI disabled — even
though build_copilot_settings_state already treats it as unconfigured. Apply the same
has_providers() check to the instance config in the proxy and edit_copilot_config paths.
Also refresh the sqlx cache for the reservation ceiling change and bump ee-repo-ref.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(frontend): migrate legacy Home filter URLs to the searchbar keys

The old Home UI stored free-text in `search`, owner scope in `filter`, and could write
`kind=all`; the generic searchbar sync uses `_default_`, `owner`, and a kind enum without
`all`. Rewrite those params once before the sync reads the URL so shared/bookmarked links
restore, and drop `kind=all` which would otherwise wedge later filter edits.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(ai): empty instance config in get_copilot_info; label user-disabled Home AI

- get_copilot_info returned any existing instance ai_config row before the free-tier
  fallback, so an empty {} disabled AI in the copilot-info UI even though the proxy now
  serves the free tier. Apply the same has_providers() gate here.
- The Home chat overlay said "No AI provider is configured" when the user had disabled AI
  in account settings (providers still present). Distinguish that state ("Windmill AI is
  disabled in your account settings") as the docked chat does, and drop the misleading
  workspace-config button in that case.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(ai): drop redundant proxy service-account check; trim TreeView comment

The service-account exclusion now lives in the free-tier helper, so the proxy calls it
directly. Also condense the tree-view resize-transition comment to the essential reason.
Bumps ee-repo-ref.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(frontend): the Home content filter is not EE-gated

ContentSearchInner loads the workspace's scripts/flows/apps/resources and matches their
contents client-side, so it works on any instance. Drop the misleading "(EE)" from the
filter label and the "EE indexer / off-EE fallback" comments.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(ee): bump ee-repo-ref for free-tier pricing + exhaustion fixes

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BSqa1iRxn9GUE9fegT7bDS

* fix(frontend): show disabled Home AI overlay statically, not on hover

The disabled-state overlay (reason + configure/add-key action) was opacity-0 and
pointer-events-none until group-hover, so keyboard and touch users saw an inert composer
with no visible remedy. Render it and the composer blur statically when disabled instead.

Also bumps ee-repo-ref for the trimmed free-tier comments.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BSqa1iRxn9GUE9fegT7bDS

* fix(frontend): give account-disabled Home AI overlay a recovery action

The account-disabled branch showed a reason but hid every action, on the mistaken premise
that account settings has no linkable route. It opens from the #user-settings hash (the
same one the sidebar Account menu uses), so link there. Bumps ee-repo-ref for the
free-tier fixes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BSqa1iRxn9GUE9fegT7bDS

* fix(frontend): gate Home AI composer for operators; a11y and filter-sync fixes

- Home composer now uses prefersSessionHandoff($userStore?.operator) instead of
  isGlobalAiEnabled(): operators reached this route and could submit a prompt into a
  /sessions page that refuses them, silently dropping it. Also drops the leftover empty
  header spacer div above the chat.
- HomeAIChat: mark the blurred/disabled subtrees inert so keyboard users can't tab into
  the unreadable textarea (pointer-events-none didn't stop Tab).
- ItemsList: keep the role-dependent searchbar keys (include_library, only_user_folders)
  in the schema unconditionally and toggle `hidden` instead, so useUrlSyncedFilterInstance
  (which snapshots the key set once) still URL-syncs a key that first appears after a
  workspace switch.
- Bumps ee-repo-ref for the indexer non-parquet build fix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BSqa1iRxn9GUE9fegT7bDS

* fix(frontend): keep CLI/MCP connect row for operators; trim filter comment

The previous commit gated all of HomeAIChat behind the operator/session check, which also
removed the AI-independent CLI/MCP "Connect workspace" drawer that operators (and the
sessions-beta opt-out) had on main. Render HomeAIChat for the same audience as before
(isGlobalAiEnabled) and gate only the composer (title, input, examples, overlay) on
operator status inside the component; the connect row always shows. Also trims the
role-dependent filter-schema comment to the <=4 line rule.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BSqa1iRxn9GUE9fegT7bDS

* fix(frontend): reconnect Home keyboard navigation to the unified searchbar

The searchbar migration replaced the <input id="home-search-input"> the ItemsList keyboard
handler keys off, so Arrow/Enter no longer drove the results list. Thread an `id` down to the
searchbar's contenteditable (via TaggedTextInput/FilterSearchbar `inputId`) so the handler and
the workspace-switch focus restoration find it again; read the caret through the Selection API
instead of an <input>'s selectionStart/End; and stand the list's arrows down while the
searchbar's suggestion dropdown is open (tracked via onDropdownVisibleChange). In free-text
mode the searchbar no longer opens its dropdown on a bare arrow key, so an empty box passes
Arrow/Enter to the list as before.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BSqa1iRxn9GUE9fegT7bDS

* fix(frontend): stop searchbar Enter inserting a newline; idle typewriter for operators

- TaggedTextInput is a single-line filter input, so Enter now preventDefaults the
  contenteditable's newline insertion (surrounding suggestion-select / list-open handlers
  still run on bubble). Previously Enter with no row highlighted dropped a literal \n into
  the query.
- HomeAIChat's placeholder typewriter effect now runs only while the composer is shown, so
  it no longer loops forever driving an unrendered input for operators.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BSqa1iRxn9GUE9fegT7bDS

* chore: update ee-repo-ref to f2a31156ac08ecb02d89dbc66d72be58e9c877ff

This commit updates the EE repository reference after PR #652 was merged in windmill-ee-private.

Previous ee-repo-ref: e59b96a2eea5d1110b40c842f17b337ab051bdd3

New ee-repo-ref: f2a31156ac08ecb02d89dbc66d72be58e9c877ff

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>
2026-08-31 20:09:53 +02:00
0c2eb0ae3d perf: add service log documents to the index one batch at a time (#10906)
* perf: add service log documents to the index one batch at a time

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014KnE8my2okxQGCx47cWMjf

* chore: update ee-repo-ref to df60763d1f243b0048dfc3fe700bc026b257bea8

This commit updates the EE repository reference after PR #762 was merged in windmill-ee-private.

Previous ee-repo-ref: f9a0b98080eecdc2885720e0f8506933a0675bb5

New ee-repo-ref: df60763d1f243b0048dfc3fe700bc026b257bea8

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>
2026-08-31 16:08:19 +00:00
Ruben Fiszelandwindmill-internal-app[bot] 831370cdde fix: harden the service log indexer's recovery and read paths (#10904)
* [ee] fix: an unreadable ingest cursor should not stop the server booting

Three follow-ups to #10894, all in the service log indexer: a corrupt cursor no
longer takes the server down at boot, the queue's writes are covered against a
real database rather than by hand, and a read skips the dedupe when the partition
it reads holds a single object.

* [ee] test: place the queue's rows relative to the clock the statement reads

Also drops the two `.sqlx` entries the query extraction orphaned: sqlx keys on the
literal including its indentation, so moving a query into a function leaves the
old copy behind.

* [ee] test: make the pair-exactness and rebuild-dedupe tests actually bite

* [ee] docs: state the cursor and dedupe rules without their history

* chore: update ee-repo-ref to 90a368362896ebcc2fcfaaf9510dc9be68c929f7

This commit updates the EE repository reference after PR #761 was merged in windmill-ee-private.

Previous ee-repo-ref: e3423705aa8f2d585bc65474cfd0c4c762ec4ad5

New ee-repo-ref: 90a368362896ebcc2fcfaaf9510dc9be68c929f7

Automated by sync-ee-ref workflow.

---------

Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-08-31 16:01:28 +00:00
aa4a6ffd66 fix: track outstanding service log files on the rows themselves (#10894)
* fix: track outstanding service log files on the rows themselves

Adds `log_file.indexed_at` so the service log ingest can read outstanding rows
instead of walking a cursor over `log_ts`. A row registered after the pass had
gone by its minute was skipped for good, and no ordering fixes that — an arrival
sequence fails the same way, since a row can take a lower value and commit after
a higher one has moved the cursor past it.

The migration marks existing rows with a sentinel; the first pass returns the
ones the old cursor had not reached to the queue.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EPAP96jJNYpPQ8bpxZcU1C

* [ee] refactor: drop the claim/confirm phase from the service log ingest queue

Two states are enough: a row is outstanding or it is marked. The migration no
longer creates the index for the claim sentinel, and the sqlx cache loses the
two queries the event-time cursor used.

* [ee] fix: make re-indexing a service log file idempotent

Corrects the `init_last_log_file_sent` note: a rewritten row keeps the
`indexed_at` it had, so one the indexers already took is not offered again.

* [ee] fix: let a rebuild take the rows it covered out of the ingest queue

Adds the query that releases them; the index layout stays v4.

* [ee] fix: index the lookup a rebuild releases rows by

A rebuild takes rows out of the queue by the file it read out of the store, which
is the one lookup that arrives without a `log_ts`. The primary key is
`(hostname, log_ts)`, so nothing covered it and each batch scanned every
outstanding row — worst in exactly the state a rebuild follows. Verified at 50k
outstanding rows: sequential scan becomes an index scan.

Also records `log_file.indexed_at` in the schema reference.

* [ee] fix: treat a state handed back without its line count as behind

* [ee] fix: give the converted state a line count

* [ee] fix: keep the converted cursor from being rewound by the rebuild

* [ee] fix: inherit the legacy cursor from one source, not field by field

* [ee] fix: count a file's lines against the buffer before reading it

* [ee] fix: bound the row buffer on what it holds, not on reported counts

* [ee] fix: settle the upgrade from the store rather than from event time

* [ee] docs: describe the conversion's second half as it now works

* [ee] refactor: settle the upgrade with one rebuild instead of reconciling

The migration records existing rows as done rather than marking them with a
sentinel: the indexer puts back what the old cursor had not reached on its first
pass, which is the only place that cursor's position is known.

* [ee] fix: repair the rows the old cursor skipped instead of recording them as done

The migration marks pre-existing rows with a sentinel again, so the indexer can
tell them from rows registered since and put the window's worth back on the queue.

* [ee] fix: keep a source file whole in one partition

* [ee] revert the file-atomic partition change

* [ee] fix: dedupe the public reads, and repair an index without a cursor

* [ee] fix: repair an index whose cursor is gone, and keep what the repair found

* [ee] fix: seed a pass from both axes of what a rebuild recovered

* [ee] fix: settle the cursor on what the store holds, not on what was read

* [ee] fix: an empty rebuild must not claim ground it has not covered

* [ee] test: pin the cursor a rebuild settles on

* chore: update ee-repo-ref to bc0c7051585194474078b6c1941a3fb73893d9e5

This commit updates the EE repository reference after PR #755 was merged in windmill-ee-private.

Previous ee-repo-ref: 328f5a90afeae9c683bf3294f0d9eb293a3e1a92

New ee-repo-ref: bc0c7051585194474078b6c1941a3fb73893d9e5

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>
2026-08-31 14:06:29 +02:00
Ruben Fiszelandwindmill-internal-app[bot] 2fb790338d upgrade argon2 to 0.6 and migrate the password hashing API (#10902)
* fix: upgrade argon2 to 0.6 and migrate the password hashing API

* test: pin that an unparseable stored hash reads as a failed login

* chore: update ee-repo-ref to 58738c39ac41d57917bbd9400318704763d997f7

This commit updates the EE repository reference after PR #759 was merged in windmill-ee-private.

Previous ee-repo-ref: 02a89fc4d27e49a494112fa91a8812e3ee4fb8a6

New ee-repo-ref: 58738c39ac41d57917bbd9400318704763d997f7

Automated by sync-ee-ref workflow.

---------

Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-08-31 08:48:27 +02:00
Diego Imbert eb95a95f48 feat(datatables): a permissions drawer on a table 2026-08-31 06:52:03 +02:00
Diego Imbert a8a8812839 feat(datatables): group grants that repeat over objects into one row 2026-08-31 06:45:37 +02:00
Diego Imbert 7b94a0fb4d feat(datatables): rename a schema from its row menu 2026-08-31 06:20:13 +02:00
Diego Imbert e989a4d9ae fix(datatables): report create privileges for the role the manager is on 2026-08-31 06:01:13 +02:00
Diego Imbert fc3732fb7f fix(datatables): repair an instance database's grant options where they are used
Handing a privilege to a role means granting it, and a privilege held without
the grant option cannot be passed on — Postgres answers such a statement with
a warning and no effect, so the roles silently receive nothing. Databases
provisioned before those options were part of the instance grants still hold
them plain.

The repair now runs on the two paths that need it, saving permissions and
applying an ACL change, rather than only on opting in to migrations: a data
table can use roles without ever touching a migration.
2026-08-31 05:55:50 +02:00
Diego Imbert 38023de582 feat(datatables): only offer create where the role may create 2026-08-31 05:52:42 +02:00
Diego Imbert e1556004f6 feat(datatables): grant database privileges from the roles drawer 2026-08-31 05:19:51 +02:00
Diego Imbert 395eac68c7 fix(datatables): give custom_instance_user the grant option on instance databases 2026-08-31 05:07:01 +02:00
Diego Imbert 924389a0ac fix(datatables): test the connection as the data table's own admin role 2026-08-31 04:39:56 +02:00
Diego Imbert 8429d909d4 fix(datatables): accept postgres 17's MAINTAIN table privilege 2026-08-31 04:33:22 +02:00
Diego Imbert bac89b0790 fix(datatables): allow deleting a migration that has already been applied 2026-08-31 04:33:22 +02:00
Diego Imbert 7effde8bbe fix(datatables): hide schemas the connection's role cannot reach 2026-08-31 04:33:22 +02:00
Diego Imbert 126204a11e fix(datatables): resolve pg_database_owner to the database's real owner 2026-08-31 04:15:21 +02:00
Diego Imbert 803e89319e feat(datatables): own and grant on a data table's schemas
A permissions drawer on a schema row: who owns it, and what each role may do
in it. Ownership moves the schema and everything already in it, and writes
the default privileges that keep the owner in reach of what the other roles
create later — an object belongs to whoever creates it, so that is the only
way to cover what does not exist yet.

Grants are built as statements — privileges, scope, role — and read back from
the catalog, per object and including default privileges, so what the page
shows is what the database has. Every change is confirmed against its own SQL
and runs in one transaction.

The editor takes a target rather than a schema: a table is the same call with
one more identifier, for when the table and role sections want it.
2026-08-31 04:03:42 +02:00
Ruben FiszelandClaude Opus 5 ac56586c0e fix: correct the service log ingest flush boundary (#10898)
Claude-Session: https://claude.ai/code/session_014KnE8my2okxQGCx47cWMjf

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-30 07:40:34 +02:00
d91ee4614a feat: day-partition the service log index and expire whole chunks (#10893)
* feat: day-partition the service log index and expire whole chunks

The service log index becomes one tantivy index per UTC day. The substance is
in windmill-ee-private#753; this side carries the EE ref and moves the log
indexer writer instead of cloning it, because sealing a chunk takes sole
ownership of its tantivy writer.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EeUmYWCJeaaHutiHLfZBQX

* fix: do not adopt the superseded watermark after an explicit index clear

A clear asks for the retention window to be read again, and a watermark says
it already has been — and the v3 copy in object storage is kept for rollback,
so it outlives the local one the clear removes. Both copies of that watermark
are now read and the newer wins, for the same reason the v4 one is taken from
the store when it is ahead: a replica that lost the lock keeps a local file
frozen where it stopped while the store went on.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EeUmYWCJeaaHutiHLfZBQX

* fix: delete a day's raw files at its checkpoint, and rebuild whole days

Bumps the EE ref for windmill-ee-private#753.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EeUmYWCJeaaHutiHLfZBQX

* fix: make an interrupted rebuild detectable, and pin the rebuild floor

Bumps the EE ref for windmill-ee-private#753.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EeUmYWCJeaaHutiHLfZBQX

* fix: keep the rebuild marker in the object store, not on local disk

Bumps the EE ref for windmill-ee-private#753.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EeUmYWCJeaaHutiHLfZBQX

* fix: two more routes to a partial index being accepted as complete

Bumps the EE ref for windmill-ee-private#753.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EeUmYWCJeaaHutiHLfZBQX

* fix: trust a local chunk only when the tracker vouches for it

Bumps the EE ref for windmill-ee-private#753.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EeUmYWCJeaaHutiHLfZBQX

* chore: condense the stale-chunk guard's doc to the four-line limit

Bumps the EE ref for windmill-ee-private#753.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EeUmYWCJeaaHutiHLfZBQX

* chore: update ee-repo-ref to 17ef439b087b400889ff19109be9d2c810142278

This commit updates the EE repository reference after PR #753 was merged in windmill-ee-private.

Previous ee-repo-ref: 3e79901b4742906d2285dd943e24fac0f735f199

New ee-repo-ref: 17ef439b087b400889ff19109be9d2c810142278

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>
2026-08-30 07:16:09 +02:00
Ruben FiszelandClaude Opus 5 7639d83a42 chore: bump ee-repo-ref to the merged EE main (#10896)
windmill-ee-private#756 was squash-merged, so the commit ee-repo-ref names is not on EE main
and the branch carrying it is gone. The content is identical, so nothing builds differently —
but a dangling ref is one garbage collection away from an EE build that cannot fetch what it
is pinned to.


Claude-Session: https://claude.ai/code/session_01WsnpNSM6K3oyjwntRwJtVN

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-29 19:45:12 +02:00
815de49e23 feat: make the service log retention period an instance setting (#10889)
* feat: make the service log retention period an instance setting

Service log retention was a hardcoded 14 days with no override, unlike job retention. It
becomes the `service_log_retention_secs` global setting (env `SERVICE_LOG_RETENTION_SECS`,
default unchanged at 14 days), reloaded on change like the other retention settings.

The constant becomes `DEFAULT_SERVICE_LOG_RETENTION_SECS` and every reader goes through
`service_log_retention_secs()`, so the `log_file` sweep, the object-storage orphan scan, the
columnar store's compaction and pruning, the retrieval clamp and the search index's trim
window all follow the configured value.

Loaded outside `initial_load`'s `server_mode` guard: a dedicated indexer trims the search
index to a window derived from this value and is not a server.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WsnpNSM6K3oyjwntRwJtVN

* fix: never let a non-positive service log retention expire every log

Every service log cutoff is `now - retention`, so a `0` or negative window puts the cutoff
at or after `now` and the next sweep reads the whole history as expired — deleting the
`log_file` rows and their object-storage files irreversibly.

`0` is reachable two ways now that the window is configurable: it is what an operator types
by analogy with the job retention period sitting directly above it, where `0` does mean keep
forever; and `SecondsInput` writes a `0` into a field that was merely focused, so saving the
Jobs panel is enough. Service logs always have a window, so clamp an unusable value back to
the default in the accessor every reader already goes through. The upper bound is where
`chrono::Duration::seconds` panics, which would abort the sweep that reads it.

The settings field rejects a non-positive value rather than silently correcting it, and its
description now names the database rows too — they are swept on every instance, including
one with no object storage configured.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WsnpNSM6K3oyjwntRwJtVN

* fix: address review findings on the service log retention setting

- Bound the monitor's `log_file` sweep. Every process rotates a log file a minute, so lowering
  the retention can make one ordinary setting change expire millions of rows; the unbounded
  `DELETE ... RETURNING` materialized all of them, and their deletion futures, in a single
  tick. Batched like the settings-page cleanup on the same table.
- Make the retention atomic private and give it one writer, so a value that would expire every
  service log cannot reach a cutoff by any path, and say so in the log when one is rejected
  rather than falling back silently.
- Cap the retention at a century. The previous ceiling only bounded `TimeDelta` construction,
  while consumers compute `now - retention`, which panics past year 262143, and build a
  Postgres interval that overflows well before the old cap.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WsnpNSM6K3oyjwntRwJtVN

* fix: cap an oversized service log retention instead of shortening it

The two unusable directions were landing on the same fallback, so configuring a retention
above the ceiling silently produced 14 days — deleting logs the operator had asked to keep
for longer. Too large now caps at the maximum, which preserves that intent; only a
non-positive value, which would expire everything and has no upward reading, falls back to
the default.

Also bound the `log_file` drain to ten batches per pass: `monitor_db` runs under a 600s
timeout that cancels every maintenance future in the same `join!` and reports a critical
error, so a backlog large enough to need batching has to drain across ticks, the way the
neighbouring sweeps already do. The settings field carries the upper bound too, and the
superseded query's offline entry is dropped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WsnpNSM6K3oyjwntRwJtVN

* fix: route the new log-file registration cutoff through the retention accessor

`send_log_files_to_object_store` arrived on main while this branch was open and reads the
retention directly. The atomic behind it is private now, so it goes through the accessor like
every other consumer — which also means the cutoff it uses to skip registering already-expired
files follows the configured retention rather than a fixed two weeks.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WsnpNSM6K3oyjwntRwJtVN

* fix: say why every mode loads the service log retention setting

A worker registers its rotated log files against the retention cutoff, so the comment naming
only the indexer no longer covers why the setting sits outside the `server_mode` guard.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WsnpNSM6K3oyjwntRwJtVN

* fix: file service log retention under Monitoring, not Jobs

Service logs are the Windmill processes' own logs — every process rotates and registers its
own, no job involved — so the Jobs panel was grouping by the shape of the widget rather than
by the subject. It sits under Monitoring now, beside the Indexer panel that holds the other
service-log window.

Its own section rather than inside that panel: the panel is badged EE, while this governs the
database sweep that runs on every instance.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WsnpNSM6K3oyjwntRwJtVN

* chore: update ee-repo-ref to a6e3533b26195918a17fea58646f71d2bbcde288

This commit updates the EE repository reference after PR #752 was merged in windmill-ee-private.

Previous ee-repo-ref: 1d93da24bd166b9a5a5cc204034a1d35ffc88474

New ee-repo-ref: a6e3533b26195918a17fea58646f71d2bbcde288

Automated by sync-ee-ref workflow.

* feat: say on the service logs page where the logs actually are

The retention number alone does not tell an operator what it governs, and the answer differs
by instance. Two states are worth calling out because they are the ones where retention does
not mean what it looks like:

Without instance object storage, each process keeps its files on its own disk. The page lists
what every host wrote, since the rows are in the shared database, but can only open the files
of the replica serving the request, and a host's files go with it when it is replaced.

With object storage but "Delete logs from s3 periodically" off — the backend default, since
uploads are gated on a store existing while deletions are gated on that toggle — expiring a
log removes the row and the local file and leaves the uploaded copy behind for good.

The retention field itself now names every copy it covers and says that full-text search
reaches back at most that far, and less when the indexer's own window is shorter.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WsnpNSM6K3oyjwntRwJtVN

* fix: describe raw log files as the transient copy they became

Retiring the raw files landed while this was being written: the indexer now deletes each one
as soon as it is ingested, and the log viewer rebuilds a file from the columnar store once the
raw copy is gone. So the durable copy is the store, and warning that an uploaded file is kept
forever when periodic s3 deletion is off only holds where no indexer runs to ingest it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WsnpNSM6K3oyjwntRwJtVN

* chore: point ee-repo-ref at the EE compile fix

EE main does not build on its own: extracting the index-window expression and adding a fourth
copy of it landed in separate PRs that never conflicted textually. windmill-ee-private#756 is
the one-line fix; this pins it so CI has a tree that compiles.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WsnpNSM6K3oyjwntRwJtVN

---------

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>
2026-08-29 19:39:14 +02:00