Opt-in, EE-gated permissions for data tables, enforced natively by Postgres
roles and row-level security. Non-admin queries connect directly as a per-user
login role (tamper-proof against arbitrary user SQL); admins connect as the
owner and keep full access. Adds a Permissions modal (Access + Row policies
tabs) to the data table settings, get/set/sync endpoints, and enforcement in
both the postgresql and DuckDB executors.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(pipelines): fork-scoped ducklake namespaces with read-defer to parent
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(pipelines): fork graph indicator + fork ducklake namespace cleanup endpoint
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(pipelines): fork_views-keyed view transition, fork lineage clone, design doc
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(pipelines): review hardening - fork DATA_PATH last-wins, registry cache TTL, defer tests
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(pipelines): per-lake isolated/shared choice at fork creation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(pipelines): chain-aware defer discovery + per-location fork namespace registry
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(pipelines): lake-scoped fork schemas, catalog identity in registry, chain-aware graph chips
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(pipelines): cleanup deletes fork data from the registered storage identity
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(pipelines): collapse fork data-path segment to one component (slash-safe ids)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(pipelines): per-catalog ancestor checks, ancestor extra_args passthrough, test compile fix
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(pipelines): invalidate fork ancestor-chain cache on lineage mutations
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(pipelines): sweep descendant ancestor-chain caches on delete/reparent
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(pipelines): run fork ducklake cleanup inline in delete_workspace
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(pipelines): resolve fork cleanup credentials pre-commit, destroy post-commit
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(pipelines): shared dev-workspace authz gate for namespace drop, invalidatable registration cache, segment-boundary delete filter
- extract require_prod_admin_for_dev_workspace, used by both delete_workspace
and drop_forked_ducklake_namespaces so the gates cannot drift
- key FORK_DUCKLAKE_REGISTERED per workspace and invalidate it in
cleanup_fork_ducklake_namespaces so a same-id fork recreated within the TTL
re-registers its namespaces
- filter listed object locations to the segment boundary before deletion
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(pipelines): keep orphaned wm-fork-* workspaces ducklake-isolated
parent_workspace_id is ON DELETE SET NULL, so a fork can outlive its
parent with an empty ancestor chain while its cloned config still points
at the shared lake. Key the isolation gate on the wm-fork- prefix as well
as the chain (mirroring workspace_is_fork): orphaned forks get the write
redirect, registration and cleanup with zero ancestors (no defer), and
keep their 'fork' graph chips.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(pipelines): attach orphaned wm-fork-* ancestors at their fork namespace
Chain position alone classified the last ancestor as a root, but an
orphaned wm-fork-* ancestor (its own parent deleted, SET NULL) ends the
chain the same way while its data lives in its fork namespace — its
descendants' defer views bound the dead root's lake instead. Key the
root-vs-fork decision on the wm-fork- prefix too, matching the
resolution gate.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(pipelines): never inherit shared lake opt-out; durable cleanup ledger for failed fork deletions
- fork creation strips cloned fork_behavior stamps before applying the
request's shared_ducklakes list: sharing is a per-creation choice, a
fork of a shared fork defaults back to isolated
- fork_ducklake_namespace loses its ON DELETE CASCADE FK: rows are the
durable cleanup ledger and outlive the workspace when physical cleanup
fails post-commit; fork creation retries leftover rows for the reused
id and refuses to create while a metadata schema still cannot be
dropped (data-file leftovers alone are inert once the schema is gone
and are swept by the next successful same-prefix cleanup)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(pipelines): make orphaned-namespace cleanup retries independent of deleted fork resources
- ledger rows gain a schema_dropped phase flag: set when the schema drop
succeeded but data cleanup failed, so later retries skip the schema
phase and need no catalog credentials at all; registration resets it
on re-attach (ON CONFLICT DO UPDATE) since attaching recreates the
schema
- retry-path $res: resolution falls back to the workspace being forked
(the deleted fork's resources were clones of a parent's); live paths
(delete_workspace prepare, drop endpoint) pass no fallback
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(pipelines): fork tables from failed-after-commit runs stay fork-owned in defer and graph
A failed materialization must not disguise a physically existing fork
table as deferred: CREATE VIEW IF NOT EXISTS silently yields to the
table, so reads hit fork data while the graph claims parent defer.
- record_mat upsert preserves the last committed snapshot_id on failure
- defer discovery and graph chips treat fork rows with a committed
snapshot as fork-owned even when status is failed
- inspect_fork_catalog also lists live fork tables (same round trip) and
the defer list is filtered against them — covers rows recorded before
this fix and tables created by raw SQL
- drop stale FK-cascade wording in the design doc and sidebar comment
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(pipelines): fork-mode ducklake settings — per-lake isolated/shared chips + banner, fork_behavior round-trip
The workspace-settings ducklake editor had no fork awareness: no
reminder of each lake's isolated/shared choice and no warning about
what edits mean in a fork. It also rebuilt each lake explicitly on
save, silently dropping fork_behavior — any settings save in a shared
fork flipped the lake back to isolated.
- fork detection mirrors the backend gate (parent link or wm-fork- prefix)
- info banner explaining isolated vs shared semantics in a fork
- per-lake chip (emerald 'isolated' / amber 'shared with parent') with
tooltips, matching the pipeline graph chip colors
- fork_behavior added to DucklakeSettingsType and preserved through
convertDucklakeSettingsToBackend
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(ducklake): scheduled lake maintenance (snapshot expiry, compaction, orphan cleanup)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(ducklake): review fixes — starts_with not LIKE, CE license-lapse escape
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(ducklake): auth-contract docs + _unchecked rename per codex review
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(ducklake): move maintenance payload construction into EE module
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(ducklake): fall through to script resolution for non-managed reserved-prefix schedules
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(ducklake): document accepted pre-existing-schedule limitation on the reserved prefix
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(ducklake): CE save-off clears the managed schedule row and queued occurrence
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore: update ee-repo-ref to 2fab310d4f50ed7c34857d69c9b854f4491bf217
This commit updates the EE repository reference after PR #645 was merged in windmill-ee-private.
Previous ee-repo-ref: fff1fd830a36beba732486f05941ec243cf6b640
New ee-repo-ref: 2fab310d4f50ed7c34857d69c9b854f4491bf217
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
edit_datatable_config only validated rename segments, not the actual
settings.datatables keys, so a data table could be saved directly under a name
like '..' or one containing '/'. Since new tables default to
migrations_enabled = true, generate_initial_datatable_migration would then
insert a migration row and the sync export would build
migrations/datatable/<name>/... paths from that name, producing malformed or
directory-escaping export paths.
Validate every persisted data table name in edit_datatable_config (alongside
the existing rename checks) and add validate_datatable_path_segment to
generate_initial_datatable_migration for defense in depth.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
datatable_database_arg eagerly resolved instance data-table credentials
(including the shared instance-wide Postgres password) and passed them as the
migration job's plaintext `database` arg, landing in v2_job.args. Since the
run route has no admin gate, a non-admin could run a migration and read
args.database to recover the password, granting cross-workspace psql access to
all instance data-table DBs.
Pass a `datatable://<name>` reference for both resource-backed and instance
data tables instead; the pg executor already resolves it to real credentials
server-side at run time, so nothing sensitive is ever stored in the job args.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(forks): clone only the current raw-app bundle, via server-side copy
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(forks): fall back to get+put when object-store copy is unsupported
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: support workspace forks on cloud using parent workspace limits
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: clarify count_paid_seats approximates rather than mirrors billing seats
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: non-admin fork UI, attach cap, and fork-count for cloud forks
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: cloud fork billing cache on rename, usage display, attach cap edge
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: fork count in cloud quotas + fork billing points to parent
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: invalidate billing/fork caches on fork deletion for id reuse
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: gate fork usage remap on CLOUD_HOSTED, not just the cloud feature
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: note cloud feature vs CLOUD_HOSTED gating in backend guide
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: reserve fork-cap slots for an attach candidate's whole subtree
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: invalidate team-plan cache on delete, raise fork depth cap
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: cap fork nesting depth (MAX_FORK_DEPTH, default 5)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: fork count/height robust to cycles and deleted intermediates
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): reset fork button loading state on creation error
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: invalidate billing cache for attached fork subtree; helper auth docs
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(forks): let partial-visibility users deploy the visible subset
The fork Compare & Deploy page hid the deploy button entirely whenever the
comparison reported any item not visible to the user (all_ahead/all_behind
flags), telling them to hand the deploy to someone with full access. But the
non-visible items are already filtered out of the diff list, and the UI already
supports deploying an arbitrary subset via per-item selection — so blocking
everything was inconsistent and, for stale/phantom rows, blocked on items that
don't even exist.
Show the deploy footer regardless; the user acts on the visible/selected items
(the per-item disabled conditions are unchanged). The hidden-items notice is
kept but downgraded to a non-blocking, direction-scoped banner that explains the
excluded items instead of removing the action.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(forks): surface hidden-item counts by kind + admin path list
WIP: expose items dropped by the visibility filter (hidden_ahead/hidden_behind
in the compare response): by-kind counts for everyone, kind+path only for admins.
* fix(forks): don't close the deployment request on a partial (hidden-items) deploy
Making the deploy button reachable in the partial-visibility case exposed a bug:
a clean merge-into-parent deploy unconditionally closed any open fork deployment
request as "merged" — marking its comments obsolete and notifying the requester
and assignees of a merge — even when hidden ahead changes were excluded from the
list and left undeployed. Only close the request as merged when the full ahead
set was visible (all_ahead_items_visible); otherwise leave it open (with a toast)
so someone with full access can finish it.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The blast-radius guard added in #9866 forced `all_ahead_items_visible` true for
any fork/target admin. But `filter_visible_diffs` keeps a modified/conflict row
(one that exists in the source AND the fork) only when the caller can see it on
both sides, so an ahead diff can be dropped for a source-side visibility gap even
when the caller is a fork admin. Forcing the flag on fork-admin alone then wrongly
reported "all ahead items visible", letting the UI enable deployment from an
incomplete comparison.
Gate the guard on admin of BOTH the source and the fork (superadmin satisfies
both), which is what actually guarantees full visibility of every item on every
side. Adds a regression test where a fork admin who is only a plain member of the
parent (no access to the item's folder) must still get `all_ahead_items_visible
= false`, plus the superadmin sanity path.
Also restores the SQLx offline cache entry for the phantom-trigger test INSERT
that #9866 landed without (CI/`SQLX_OFFLINE=true` builds failed on it), and adds
entries for the new test's all-literal queries.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Deleting a trigger left a stale `workspace_diff` row: `delete_trigger` (the
generic TriggerCrud handler) was the only delete path that never called
`handle_deployment_metadata`, unlike every other kind. Because
`compare_workspaces` trusts a cached `has_changes=true` row for non-script/flow
kinds and the visibility filter then drops it (the trigger no longer exists), a
deleted trigger became a phantom "ahead" item that flipped
`all_ahead_items_visible` to false — hiding the deploy button and showing a
"changes not visible to your user" warning that even a superadmin could not
resolve (`reset_diff_tally` doesn't clear a `has_changes=true` row either).
- delete_trigger now re-tallies via handle_deployment_metadata, so the next
compare re-evaluates and corrects/removes the row (matches resource/variable/
folder/schedule deletes).
- compare_workspaces forces the visibility flags true per side for anyone who
sees that side in full: target/fork admin (or superadmin) for ahead items,
source/parent admin (or superadmin) for behind items. The flag is a pure
visibility guarantee — the deploy itself is authorized separately — so for
such users a dropped diff is provably a phantom, never a permission gap.
- Add a regression test asserting a phantom trigger diff row no longer blocks a
superadmin while still (conservatively) warning a partial-context user.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(workspaces): enforce CE workspace limit when unarchiving
Unarchiving a workspace re-activates a soft-deleted (deleted = true)
workspace, effectively bringing it back to the active set. On CE this
bypassed the 2-workspace cap that create_workspace enforces, letting a
user exceed the limit by archiving and re-unarchiving.
Run the same _check_nb_of_workspaces guard before flipping deleted back
to false. The workspace being restored is still deleted = true at that
point, so it is correctly excluded from the count.
Fixes WIN-2119
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(workspaces): cap CE archived workspaces at 1
Complements the unarchive-limit fix: without a cap on archived
workspaces, a CE user could stockpile many soft-deleted workspaces (each
of which still occupies its workspace id and can later be unarchived).
Refuse a new archive on CE when an archived workspace already exists,
mirroring the create/unarchive workspace-count guards.
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: add dev workspaces paired with a lockable prod workspace
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: gate dev-workspace prod-lock on admin and prevent attach cycles
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat: redirect locked-prod edits into the dev workspace
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: make dev-workspace settings tab available on CE (was EE-gated)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat: lock prod against forking too and funnel edits to the dev workspace
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat: open dev item page on edit and tailor dev-workspace lock messages
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat: prevent nested dev workspaces and hide dev option when one exists
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore: drop the redundant already-has-dev hint on the fork form
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat: badge dev workspaces and sort them ahead of forks in the tree/switcher
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat: label dev workspaces as 'Dev workspace of X' instead of 'Fork of X'
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat: label edit as 'Edit in <dev>', cover editor headers, auto-expand dev in tree
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat: split prod lock into separate block-deploy and prevent-forking toggles
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat: make resources/variables workspace-specific from compare page
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat: steer AI-chat sessions to the dev workspace
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat: refine session fork options and lock guidance for dev/prod
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: session picker reads prod's real rules, default to current ws
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat: copy members into forks and clarify dev-workspace root labeling
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* style: place the workspace id field under the fork name
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: address dev-workspace review findings and harden fork detection
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore: regenerate sqlx offline cache
Restores entries dropped during the origin/main merge and adds the
dev-workspace queries (is_dev_workspace, ws_specific, has_parent).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: address second-round dev-workspace review findings
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: address Pi and Codex review findings on dev-workspace endpoints
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: gate locked-dev git-branch fork on admin and validate ws_specific path
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: clear prod dev-lock when deleting an attached dev workspace
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor: consolidate dev-workspace migration and scope all-group join to attach
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: restore dev-workspace CHECK into consolidated migration and scope all-group join
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor: drop copy_members from the dev-workspace attach path
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: dev-workspace lifecycle/auth fixes from Codex review round
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat: explicit create-in-other for workspace-specific items
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: make create-in-other strictly create-only (never overwrite target)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: return 403 (not 401) for dev-workspace permission denials
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat: allow attaching a same-family fork as a dev workspace
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* style: emphasize the go-to-dev action in the no-direct-deploy alert
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: seed a resource's linked variables when creating it in the other workspace
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: judge workspace deploy/fork locks against the user's identity in that workspace
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* style: clarify create-in help text in workspace-specific panel
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: admin-gate dev-workspace creation and harden lock/seed edges
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: preserve a staged fork's source on picker create-mode re-entry
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: clear dev flag on archive and check dev existence server-side
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: make create-in-other atomically create-only via direct create
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: create-only resource insert, ws-specific list scopes, archive lock guard
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: reserve the dev_workspace_lock protection-rule name from the public API
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs: reattach create_protection_rule doc comment to its function
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor: make dev-archive pairing teardown atomic with the archive
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: follow deploy_to on root rename; show dev pairing to non-member prod admins
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: copy creator metadata on fork; invalidate fork routing cache on rename
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: accept g/ paths in set_ws_specific; gate copy_members to dev workspaces
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* perf: eliminate dual-connection DB pool contention across worker, queue, and api
Reuse the held transaction (or move pool reads before begin()) instead of
checking out a second pool connection while a tx is open, extending the
fix from #9789/#7861. Targets the per-worker pool (max 5) hot paths plus
several server-pool API handlers.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: pass owned pool to get_email_from_permissioned_as in http trigger handler
The generified signature takes impl PgExecutor; the http trigger handler
passed &db where db is already &DB, yielding &&Pool which does not impl
PgExecutor (only surfaced under the full feature set in CI).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: keep RLS-exposed reads on the non-RLS pool and isolate flow-eval reads in a savepoint
Addresses review of the dual-connection sweep:
- worker_flow: wrap the stop_after_all_iters_if reads in a SAVEPOINT. The
caller swallows the error and keeps using tx, so a DB read failure must
not leave the outer transaction aborted (it would fail the later commit).
Matches the previous pool-read semantics.
- Revert reads that were moved onto an RLS (user_db) transaction back to the
non-RLS pool, since RLS row-visibility/role context can change results:
push_scheduled_job (email/tag/settings lookups; reachable with a user_db
tx from api-schedule/api-flows), push_inner native-retry dedicated_worker
routing (RLS isolation variants), resources.rs app-namespace folder
auto-create (non-admins must not be blocked), and the script archive/delete
UPDATEs. Non-RLS db.begin() reuse and move-before-begin are kept.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test: failpoint proving the stop_after_all_iters_if savepoint isolates an aborted read
Adds a worker-crate failpoints feature and a data-driven hook: when the
stop_after_all_iters_if expr is the magic sentinel, the in-evaluation read runs
SELECT 1/0 to abort its (savepoint) transaction. The test asserts the flow still
completes (iteration marked failed) — which only holds if the savepoint keeps the
outer status-update transaction committable. Without the savepoint the abort would
poison the outer tx and the job would never complete.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* perf: drop v2_job side-table ON DELETE CASCADE FKs to speed retention deletes
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: document delete_jobs auth contract and workspace-scope jobs_export purge
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: scope AI sessions per workspace family with lifecycle reconcile
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor: centralize session reconcile trigger + extract pure lifecycle decision
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* perf: remove unused workspace family index
* refactor: scope sessions by workspace root id, drop family_id column
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(sessions): preserve user-archived sessions when archiving their workspace
archiveSessionsForWorkspace tagged every session archivedByWorkspace, including ones the user had already archived by hand, so a later workspace unarchive auto-restored them. Skip already-archived sessions so only workspace-archived ones are tagged, matching decideSessionLifecycle.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: archived-session banner with unarchive, suppress workspace-gone banner while archived
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: re-root sub-fork sessions on reconcile when an ancestor is deleted
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: group AI sessions by workspace family with show-all-workspaces filter
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: revert unrelated AIProviderPicker cosmetic changes
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: hide per-session unarchive when workspace is gone, show move/discard instead
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: GC attached files on lifecycle delete + reconcile on sidebar fork delete
Addresses Codex review: deleteSessionsForWorkspace/reconcile delete now GC linked files (deleteItemsForSession), matching deleteSession; sidebar deleteFork now reconciles so surviving child forks re-root off the deleted ancestor. Also de-flaked post-rehydrate reads in the IndexedDB tests via vi.waitFor.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: don't strand user if post-delete reconcile throws; refresh stale warmSessions comment
Addresses auto-review P2s: wrap reconcileAfterWorkspaceChange in deleteFork so the parent switch + navigation always runs even on reconcile failure; correct the warmSessions comment which no longer holds under 'Show all workspaces'.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: don't fail/strand fork archive+delete when client session cleanup throws
Addresses cubic P1/P2 on forks/compare: the workspace archive/delete is authoritative; wrap the best-effort session cleanup + reconcile so a local IndexedDB failure neither falsely reports failure nor blocks navigation away from the gone fork. Mirrors the SidebarContent fix.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: drop drafting-history aside from reconcileAfterWorkspaceChange comment
Addresses auto-review P2: keep the refresh-before-reconcile invariant, drop the 'which they did inconsistently' narration per AGENTS.md (comments record constraints, not drafting history).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: clean up sessions on fork-id reuse + make all workspace-mutation cleanup best-effort
Addresses Codex P1s: (1) CreateWorkspaceInner 'permanently delete existing fork' (id-reuse) now drops local sessions for that id so they don't resurface on the recreated fork; (2) workspace_settings archive/delete and SidebarContent child-delete loop + main delete now treat post-mutation session cleanup as best-effort, so a local IndexedDB failure can't strand the user or abort remaining deletes (matching the compare-page fix).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: make fork-reuse session cleanup fire-and-forget (non-blocking)
Addresses cubic P2: don't await the best-effort cleanup so a slow IndexedDB op can't block the delete/reuse flow.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: drop previous user's transient drafts on user change
Addresses Pi P1: hydrateSessions preserved transient (unsent) drafts across user changes, so user A's draft + its pending fork/workspace state bled into user B's list and got reused by createSession. onUserChange now drops transients when the email changes; reconcile (intra-user) still preserves them. Regression test added.
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 workspace_id column on unique_ext_jwt_token (migration 20260409145556)
has no FK constraint on the workspace table, and delete_workspace did not
remove its rows. Deleted workspaces left orphaned external JWT token records
that kept appearing in the superadmin External JWTs listing.
Add a DELETE FROM unique_ext_jwt_token WHERE workspace_id = $1 alongside the
other per-table cleanup statements in delete_workspace.
Fixes WIN-2078
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(workspaces): add skip_email option to invite_user and add_user endpoints
The workspace invite_user and add_user API endpoints unconditionally sent
notification emails when SMTP was configured, with no way to suppress them
per-request. This is noise for automated workflows that programmatically add
users to workspaces.
Add an optional `skip_email: Option<bool>` field to `NewWorkspaceInvite` and
`NewWorkspaceUser`, following the existing pattern on `NewUser` used by
POST /api/users/create, and guard the `send_email_if_possible` calls with
`if !nu.skip_email.unwrap_or(false)`. The field is optional, so existing
clients are unaffected.
The auto-add code paths in workspaces_ee.rs (domain-based and instance-group
auto-add) are auto-triggered and take no API parameter, so they are left as-is.
Fixes WIN-2068
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(workspaces): make workspace invite/add emails toggleable via instance setting
Replace the per-request skip_email approach with an instance-level setting
`disable_workspace_invite_emails`. When enabled, the email notifications sent by
the workspace invite_user and add_user endpoints are suppressed. Useful for
instances where users are added programmatically (e.g. CI pipelines that fork
workspaces and add users) and the invite emails are noise.
Backend:
- Add `DISABLE_WORKSPACE_INVITE_EMAILS_SETTING` global setting constant.
- Guard the `send_email_if_possible` calls in invite_user and add_user with a
read of that setting (via the existing `load_value_from_global_settings`
helper). Defaults to false, so existing behavior is unchanged.
- Revert the per-request `skip_email` field on NewWorkspaceInvite /
NewWorkspaceUser and the corresponding openapi additions.
Frontend:
- Expose the setting as a boolean toggle in the SMTP tab of the instance
settings (superadmin).
The auto-add paths in workspaces_ee.rs are unaffected.
Fixes WIN-2068
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): gate disable_workspace_invite_emails toggle behind EE
Email delivery (send_email_if_possible) is a no-op outside the EE/private
build, so the toggle has no effect on a pure-OSS instance. Add `ee_only: ''`
to match the sibling SMTP settings: the toggle is grayed out (with an EE badge)
on non-EE instances instead of rendering as an active no-op control.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): don't EE-gate disable_workspace_invite_emails toggle
The earlier ee_only addition was based on the false premise that the
workspace invite/add emails are license-gated. They are not: SMTP
configuration (SmtpSettings) and email sending (send_email_if_possible)
have no enterpriseLicense check — they only require the closed-source
build with SMTP configured. The sibling smtp_settings carries ee_only: ''
but its smtp_connect field renders no SettingCard label, so that flag is
inert (no badge, no disable). On a plain boolean field ee_only is fully
active, which incorrectly grayed out the toggle and showed an EE badge.
Drop ee_only so the control matches the actual non-license-gated behavior.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>