Commit Graph

17960 Commits

Author SHA1 Message Date
5ca53ef994 fix: Add defense in depth user_id filter to handle list tools MCP handler (#13373)
* feat(mcp): add defense-in-depth user_id filter to handle_list_tools for consistency with other MCP handlers

* [autofix.ci] apply automated fixes

---------

Co-authored-by: Janardan S Kavia <janardanskavia@Janardans-MacBook-Pro.local>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-05-28 18:56:02 +00:00
c9fe220d63 feat(memory): add inline docs link to preprocessing instructions tooltip (#13352)
* feat(memory): add inline docs link to preprocessing instructions tooltip

* add tooltip link for example

* remove duplicate key

---------

Co-authored-by: Olayinka Adelakun <olayinkaadelakun@Olayinkas-MacBook-Pro.local>
Co-authored-by: Olayinka Adelakun <olayinkaadelakun@mac.war.can.ibm.com>
2026-05-28 18:32:25 +00:00
c0b4b05d41 docs: update database tables (#13254)
update-database-tables
2026-05-28 18:12:03 +00:00
aafce557aa feat(authz): admin APIs, share-aware build_flow, and base service additions (#13371)
* feat(authz): add roles, role-assignments, teams, and me/permissions APIs

Completes the OSS admin surface for RBAC: full CRUD on authz_role,
authz_role_assignment, authz_team, and authz_team_member tables, plus a
per-user effective-permissions endpoint backing the frontend permission gate.
Plugin (enterprise Casbin) is invalidated on every write so the next enforce
sees the change.

New endpoints:
* GET/POST/PATCH/DELETE /api/v1/authz/roles -- custom-role CRUD with parent
  cycle detection, system-role protection, and FK-aware delete (409 when
  assignments still reference the role).
* GET/POST/DELETE /api/v1/authz/role-assignments -- list filterable by
  user/role/domain; superuser-only for write, self-read allowed.
* GET/POST/PATCH/DELETE /api/v1/authz/teams + GET/POST/DELETE
  /api/v1/authz/teams/{id}/members -- backs the share-with-team flow and
  the team admin UI.
* POST /api/v1/authz/me/permissions -- returns per-resource allowed actions
  for the current user (capped at 500 ids), backs the FE permission gate.

Base service additions (lfx.services.authorization.base):
* list_visible_resource_ids -- plugin SQL prefilter for list endpoints; OSS
  returns None ("no prefilter, fall through to filter_visible_resources").
* get_effective_permissions -- default impl uses batch_enforce so plugins
  inherit it for free; Casbin plugin can override for a tighter query.

* fix(authz): make build_flow share-aware so shared flows can be run by non-owners

The build_flow handler was loading flows with an owner-OR-public SQL filter,
short-circuiting the ensure_flow_permission(EXECUTE) check on private flows
shared with the current user. The existing in-code comment flagged this as a
Phase 3 prerequisite for cross-user build support.

Switch to the share-aware load helper (_read_flow / authorized_or_owner_scoped):
* When the registered authorization plugin signals SUPPORTS_CROSS_USER_FETCH
  (enterprise Casbin), the row loads by id alone and the plugin decides via
  enforce(). OSS pass-through keeps the historical owner-only behavior.
* PUBLIC flows are still buildable by any authenticated user in both modes
  (preserved via the explicit access_type=PUBLIC fallback).
* Plugin deny is translated to 404 (not 403) via deny_to_404 so the response
  is indistinguishable from "flow does not exist" and the caller cannot
  enumerate UUIDs by probing for 403 vs 404.

* test(authz): cover new admin routes and share-aware build_flow

Pins the security floor and validation behavior added in the prior commits so
they don't silently regress. Uses the existing fake async session + stub authz
service pattern from test_authz_share_routes.py.

test_authz_admin_routes.py (17 tests):
* /authz/roles -- superuser gate on create, persist + invalidate_all, 409 on
  name conflict, system-role protection on update + delete, self-parent and
  parent-cycle rejection, 409 when deleting a role with active assignments.
* /authz/role-assignments -- non-superuser blocked from listing other users'
  assignments; self-list allowed; 404 when user_id is unknown; create
  invokes invalidate_user(target).
* /authz/teams -- superuser gate on create, add_member invokes
  invalidate_user(target), duplicate add returns 409.
* /authz/me/permissions -- correct per-resource shape, 400 when over 500
  ids, empty request returns empty.

test_chat_build_flow_authz.py (5 tests):
* Owner happy path preserved.
* Plugin 403 deny converted to 404 (UUID privacy).
* Unknown flow + no PUBLIC fallback -> 404.
* Share-aware load miss + PUBLIC fallback hits -> success.
* Non-403 errors (e.g. 500) pass through with status preserved.

Total: 22 new tests, all passing locally.

* fix(authz): normalize, dedupe, and cap the actions list on /authz/me/permissions

The resource_ids cap (500) bounded one side of the cartesian product, but the
actions list was unbounded. A client could submit actions=["read"] * 100000
and trigger a 50M-entry batch_enforce, plus the same action in different
casings ("READ", "read") would have been treated as distinct entries.

Add a field_validator on EffectivePermissionsRequest.actions that:
* strips whitespace and lowercases each entry
* de-duplicates while preserving order
* caps the result at 10 unique actions (headroom over the 6 known actions:
  read, write, execute, delete, create, manage)
* returns None when the normalized list is empty so the handler still falls
  back to _DEFAULT_ACTIONS

Over-cap requests raise ValidationError (HTTP 422 at the request boundary),
which matches Pydantic conventions for input shape problems and differs from
the resource_ids cap (HTTP 400 in the handler) by design -- bound-violation
on list size is structurally a validation error, not application logic.

6 new tests: normalization+dedupe, empty-collapses-to-None, over-cap rejection,
50-entry dedupe stays under cap, None stays None, handler sees normalized list.

* fix(authz): default /authz/role-assignments list to caller's own assignments

The gate `if user_id is None or user_id != current_user.id` required superuser
whenever user_id was omitted, so any non-superuser calling
GET /authz/role-assignments with no query params got 403 even though the
docstring promised "Users may query their own assignments".

Resolve user_id to current_user.id when omitted, then only gate superuser when
the resolved id differs from the caller. The query is now always filtered by
the resolved user_id, so admins doing cross-user lookups make one call per
user instead of relying on an unscoped see-all.

Adds test_list_assignments_no_user_id_defaults_to_self pinning the new
behavior. The other two assignment-list tests (self with explicit user_id,
non-superuser blocked from another user) remain unchanged and still pass.

* fix(authz): use model_fields_set so PATCH /authz/roles can clear nullable fields

The "if payload.<field> is not None" guards made it impossible to clear
description or parent_role_id via PATCH -- an explicit "description": null
in the body was indistinguishable from omitting the field, leaving the old
value in place.

Switch to presence checks via payload.model_fields_set:
* description: nullable on the DB side, so an explicit null clears the field.
* parent_role_id: nullable + SET NULL on cascade; null removes the parent.
* name: NOT NULL + unique on the DB side. The presence check applies, but
  an explicit null is rejected at the boundary with HTTP 400 so the caller
  sees a clear error instead of an opaque 409 "Name conflict" from the
  IntegrityError catch block below.
* permissions: nullable=False (default_factory=list) on the DB side. An
  empty list is the natural "clear"; null is rejected with HTTP 400.

6 new tests: clear description via null, clear parent via null, omitted
fields untouched, reject null name, clear permissions via empty list,
reject null permissions.

Adds _make_role_row helper so SimpleNamespace fakes carry every field
that RoleRead.model_validate expects (workspace_id, created_at, created_by).

* fix(authz): use model_fields_set so PATCH /authz/teams can clear description

The "if payload.description is not None" guard made the description field
unclearable via PATCH -- an explicit "description": null was indistinguishable
from omitting the field, leaving the old value in place.

Switch to a presence check via payload.model_fields_set so null clears the
field, while omission still leaves the row alone. The DB column is nullable,
so None is a legitimate value here.

Scope intentionally narrow: team_name and adom_name are NOT NULL on the DB
side (null would crash on commit) and is_active is bool (null doesn't model
any state), so they keep the existing is-not-None guards.

Adds 2 tests: clear via explicit null, omission leaves value untouched.
Adds _make_team_row helper for parity with _make_role_row.

* fix(authz): validate batch_enforce result length in get_effective_permissions

The default get_effective_permissions impl built `requests` as a cartesian
product of resource_ids x actions, called batch_enforce, then sliced the
flat result back into per-resource action lists. If a plugin's batch_enforce
returned the wrong number of results -- contract violation -- the slicing
silently produced incorrect mappings:

* too-short flat: later resources got `[]` (out-of-bounds slice returns empty
  in Python) instead of their real permissions
* too-long flat: tail entries silently dropped

The inner `zip(..., strict=True)` only caught the per-slice mismatch when a
slice happened to be too short, not the global length mismatch.

Validate `len(flat) == len(requests)` immediately after the call and raise
ValueError with expected/actual counts so plugin authors find the contract
break at the first wrong call, not via mysteriously-empty permission lists.

Adds two lfx tests:
* default impl returns full action list per resource (no enforcement)
* mocked batch_enforce returning a wrong-sized list raises ValueError

* fix: remove EE references

* fix: harden authz admin APIs based on PR review

- Pin permission slug format to canonical <resource>:<action> and validate
  RoleCreate/RoleUpdate at the API boundary so bad slugs cannot land in
  authz_role.permissions and silently fail policy sync.
- Tighten RoleAssignmentCreate: domain_type is a Literal and a model
  validator enforces domain_id consistency (required for org/workspace/
  project, forbidden for global).
- Restore _resolve_casbin_domain re-export for plugin backward compat.
- Audit-log policy-changing admin writes (role CRUD, role-assignment
  create/delete, team CRUD, team membership add/remove) so privilege
  grants are visible alongside enforcement decisions.
- Invalidate authorization cache when a team's adom_name or is_active
  changes; pure display edits still skip the flush.

* review feedback

* [autofix.ci] apply automated fixes

* fix(authz): harden build_flow and expose authz_enabled on config

Block non-owners from overriding flow graph data on shared builds,
revert unrelated component_index.json churn, and surface
LANGFLOW_AUTHZ_ENABLED on public and authenticated config responses
for EE UI gating.

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: himavarshagoutham <himavarshajan17@gmail.com>
Co-authored-by: Himavarsha <40851462+HimavarshaVS@users.noreply.github.com>
2026-05-28 17:45:41 +00:00
e2ef18104b chore(frontend): Clean up biome violations on release-1.10.0 (#13375)
fix biome lint
2026-05-28 13:42:01 -03:00
b63e98ba06 feat(memory): default session filter to All Sessions and add it as a selectable dropdown option (#13350)
* feat(memory): default session filter to All Sessions and add it as a selectable dropdown option

* [autofix.ci] apply automated fixes

* add translation

* [autofix.ci] apply automated fixes

* improve translations

* [autofix.ci] apply automated fixes

* improve translations

* [autofix.ci] apply automated fixes

* fix testcase

* [autofix.ci] apply automated fixes

---------

Co-authored-by: Olayinka Adelakun <olayinkaadelakun@Olayinkas-MacBook-Pro.local>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Olayinka Adelakun <olayinkaadelakun@mac.war.can.ibm.com>
2026-05-28 15:42:16 +00:00
2b8ded4664 fix(chat): align message card surfaces (#13354)
* fix(chat): align message card surfaces

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-05-28 15:37:27 +00:00
e4d5190be4 chore(ci): Fix 403 in DB migration validation witness flow (#13376)
fix auto login migration validation
2026-05-28 08:32:20 -03:00
6b2ca4bcd8 fix: restore qdrant deps and migration workflow 2026-05-27 20:09:57 -07:00
ebb20c58b0 fix: Restore custom Agent prompts in starter projects (#13282)
* fix templates prompts

* add regression test

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-05-27 20:59:37 -03:00
1e75a269c8 fix: Run Flow mutable state leak (#13340)
* fix: isolate Run Flow component state

* chore: auto-bake note keys and regenerate backend locales/en.json [skip ci]

* chore: wake ci

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

* fix: tune component template copying

* [autofix.ci] apply automated fixes

* fix: preserve explicit load-from-db state

* fix: file component error in run flow

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-05-27 22:43:39 +00:00
104a83c83c docs: lfx bundles (#13142)
* docs-initial-overview-bundles-content

* fix-broken-link

* remove-production-page

* docs: check bundles against code and cleanup

* docs: fix links
2026-05-27 20:16:19 +00:00
a7b8170d53 fix: Force streaming on AgentComponent regardless of toggle (#13358)
* fix streaming default true on release

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

* add robust tests around streaming to avoid any future errors

* [autofix.ci] apply automated fixes

* improve fe flaky tests

* fix outdated action test

* fix flaky outdated action tst

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-05-27 18:43:10 -03:00
1bb95275a5 docs: openrouter promoted to global provider (#13271)
docs: check openrouter content and add global
2026-05-27 18:40:35 +00:00
e694bbcdff docs: troubleshooting for container mismatch (#13217)
* add-troubleshooting-for-container-mismatch

* grep-for-volume-name

* fix-spacing

* danger-admonition
2026-05-27 18:40:16 +00:00
9b7486efe8 docs: macos support matrix (#13363)
* docs: macos support matrix

* docs: fix linking errors and clarify limitations

* cleanup
2026-05-27 18:19:56 +00:00
5e86ab6c5f feat: accept v2 workflow globals in request body (#13351)
* fix: accept v2 workflow globals in request body

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

* review feedback

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-05-27 13:01:00 -07:00
fb3d6ec90b feat: OSS authorization foundations for enterprise RBAC (#13153)
* feat: add OSS authorization foundations for enterprise RBAC

Introduce pluggable AuthorizationService, authz plugin tables, and flow
API enforcement hooks. Disabled by default (LANGFLOW_AUTHZ_ENABLED=false);
enterprise Casbin plugin can override via lfx.services entry point.

* fix(authz): allow non-superusers when OSS authorization service is active

OSS LangflowAuthorizationService is a pass-through until enterprise Casbin
replaces it; the previous fail-closed superuser-only check caused 403 on flow APIs.

* fix(authz): harden OSS authorization foundations (bug + tests + docs) (#13216)

* fix: harden OSS authorization foundations (PR (a) — bug + lint + tests)

Hardening pass for #13153 to clear pre-merge checks and raise unit-test
coverage on the new authorization surface before broader feature work.

- Fix read_public_flow: query the flow directly and return when
  access_type == PUBLIC; remove user-impersonation via
  get_user_by_flow_id_or_endpoint_name and the read_flow delegation
  (public means public — no authz call).
- Fix _token_alias_candidates ordering so the _access_token branch is
  reachable (previously shadowed by the _token check).
- Register BaseAuthorizationService in import_all_services_into_a_dict so
  AuthorizationServiceFactory can resolve its create() return annotation
  at startup (mirrors the BaseAuthService pattern).
- Rename test_non_wxo_access_token_does_not_create_bearer_alias to
  test_generic_access_token_exposes_bearer_alias; body asserts the alias
  IS created.
- Uppercase env-var literals in test_minimal_services to clear
  Ruff SIM112/S105; per-line noqa only for the lowercased global header
  name x-langflow-global-var-access-token.
- Add unit coverage for batch_enforce (enabled/disabled/empty),
  get_allowed_actions, ensure_permission, ensure_flow_permission,
  AuthorizationServiceFactory.create, and the LFX no-op default
  AuthorizationService.
- Persist+read tests for the seven previously-untested authz models
  (AuthzRoleAssignment, AuthzTeam, AuthzTeamMember, AuthzShare,
  AuthzEditLock, AuthzAuditLog).
- One-line docstrings on undocumented public symbols across the new
  authorization modules and authz database models.

* fix(authz): align authz_role.permissions model nullability with migration

Migration f7a8b9c0d1e2 declares ``permissions`` as ``nullable=False`` but
the model used the default ``Column(sa.JSON)`` (nullable=True), which made
``test_no_phantom_migrations`` detect a phantom modify_nullable diff after
upgrading to head. Explicitly set ``nullable=False`` so the model matches
the migration.

* feat(authz): finish Phase 1 OSS — FlowAction enum, audit, owner override, project domain (#13220)

PR (b) of the OSS authz rollout. Lands every piece the enterprise Casbin
plugin needs to enforce RBAC on flows end-to-end:

- New FlowAction(str, Enum) — canonical action vocabulary (read/write/
  create/delete/execute/deploy). Re-used by enterprise PolicySync when
  compiling roles + shares into casbin_rule.

- ensure_flow_permission now accepts FlowAction|str, computes domain
  from folder_id ('project:{folder_id}' or '*'), and applies an owner
  override: when current_user.id == flow.user_id, the call short-circuits
  before contacting the plugin (so solo installs keep working with
  LANGFLOW_AUTHZ_ENABLED=true). Owner-override is audited too, with
  result='owner_override'.

- Guards wired on all 9 flow CRUD routes (was 4): create, read, patch,
  delete, put-upsert (write|create branches), batch-create, upload,
  bulk-delete, bulk-download. /public_flow/{id} stays unguarded by
  design (public means public), and GET /flows/ list-filtering is
  deferred to a follow-up PR.

- audit_decision() schedules an AuthzAuditLog row via asyncio.create_task
  on every allow/deny/owner_override decision. Failures inside the task
  are logged and swallowed so audit never blocks a request. Gated on a
  new AUTHZ_AUDIT_ENABLED setting (defaults True; ignored when AUTHZ is
  off).

- filter_visible_resources() helper — generic batch_enforce-based filter
  for list endpoints. No-op when AUTHZ disabled. Plumbing for PR (c);
  GET /flows/ rewrite is intentionally NOT in this PR.

- BaseAuthorizationService gains invalidate_user / invalidate_role /
  invalidate_all hooks (no-op default bodies). Enterprise plugin
  overrides these for L1/L2 cache invalidation when roles or shares
  change.

OSS LangflowAuthorizationService remains a pass-through; enforcement is
entirely the enterprise plugin's job. Tests use a stub fail-closed
service via monkeypatch to verify guards forward the right tuple, and
source-level checks on flows.py ensure no route silently regresses to
a bare-string action or loses its guard.

47 backend + 12 lfx new/extended tests. Migration regression suite
still green.

* feat(authz): add deployment API guards aligned with Phase 1 flow authz

Wire ensure_deployment_permission on deployment CRUD and run routes using
DeploymentAction, project/workspace domain resolution, owner override, and
audit logging consistent with Eric's flow authorization helpers.

* feat(authz): add `langflow authz dry-run` CLI for policy simulation (#13224)

Adds a self-contained subcommand that simulates every flow-CRUD guard
site under a configurable stub policy and prints what *would* happen —
the Casbin tuple (user, domain, obj, act), the audit row that would be
written, and the resulting HTTP outcome. No network, no DB writes.

Useful for:
  - validating an upcoming Casbin policy before shipping it
  - showing operators what the audit log will look like
  - smoke-testing the guard wiring after a refactor

Built-in policies:
  - allow-all       (matches the OSS default)
  - deny-non-owner  (owners + superusers allowed)
  - deny-writes     (read/execute allowed; write/create/delete denied)
  - owner-only      (only the flow owner; no superuser bypass)

Usage:
  langflow authz dry-run
  langflow authz dry-run --policy deny-writes
  langflow authz dry-run --policy deny-non-owner --json --output report.json

Implementation: monkey-patches the three helpers in
langflow.services.authorization.utils (get_settings_service,
get_authorization_service, audit_decision) for the duration of the run,
walks _FLOW_GUARDS x three actors (owner / non-owner / superuser), and
renders a Rich table or JSON document with per-row decision + summary
footer.

Tests: 18 cases covering each stub policy, the runner output shape, JSON
serialisation, and CLI integration via typer.testing.CliRunner.

* fix(authz): keep user-derived is_superuser authoritative in ensure_permission

Caller-supplied context was merged after `_auth_context(user)`, which let a
caller forge `context={"is_superuser": True}` and bypass intent of the guard.
Flip the merge order so user-derived auth fields always win, and add a
regression test that asserts a malicious caller cannot override the flag.

* fix(authz): per-flow CREATE checks in batch and upload paths

The batch (POST /flows/batch/) and upload (POST /flows/upload/) handlers were
calling ensure_flow_permission once at the route boundary without per-flow
scope. Because the FlowCreate payload carries caller-supplied workspace_id and
folder_id, a single coarse check let payloads target workspaces/folders the
caller has no create permission on — invisible under OSS pass-through but a
real bypass once an enterprise plugin enforces workspace-scoped roles.

Both handlers now iterate flow_list.flows and call ensure_flow_permission with
the flow's own workspace_id and the effective folder_id (the query parameter
wins on upload, matching _upsert_flow_list's actual write behavior).

Adds two AST-level regression tests that assert each route calls
ensure_flow_permission inside a `for ... in flow_list.flows` loop with the
flow's destination kwargs.

* fix(authz): authorize destination scope when PATCH/PUT moves a flow

update_flow (PATCH) and upsert_flow's update branch (PUT) only checked WRITE
permission against the existing flow's workspace_id/folder_id. Because
_patch_flow and _update_existing_flow apply payload workspace_id/folder_id via
model_dump(exclude_unset=True, exclude_none=True), a caller authorized to
write a flow in scope A could move it into scope B without ever being checked
against B — invisible under OSS pass-through but a real bypass once an
enterprise plugin enforces workspace/share-scoped roles.

Both handlers now compute the effective destination (payload wins when
non-None, else existing) and, when it differs from the existing scope, run a
second ensure_flow_permission(WRITE, ...) call against the destination. No
extra DB hits — the audit row already captures move attempts.

Adds two AST-level regression tests asserting each route makes the
destination check using target_workspace_id/target_folder_id kwargs.

* fix(authz): partial unique indexes on authz_role_assignment for NULL domain_id

The original UNIQUE(user_id, role_id, domain_type, domain_id) constraint on
authz_role_assignment allowed duplicate global role assignments because SQL
treats NULL domain_id values as never-equal. Two ("global", NULL) rows for the
same (user, role) pair both passed.

Replaces the broken constraint with two partial unique indexes:
- uq_authz_role_assignment_scoped: unique on (user_id, role_id, domain_type,
  domain_id) WHERE domain_id IS NOT NULL — covers workspace/org assignments.
- uq_authz_role_assignment_global: unique on (user_id, role_id, domain_type)
  WHERE domain_type='global' AND domain_id IS NULL — covers global assignments
  without relying on NULL equality.

Postgres and SQLite both support partial unique indexes via WHERE clauses.

New Alembic migration (Phase: EXPAND) drops the broken constraint and creates
the two indexes, using batch_alter_table so SQLite recreates the table cleanly.

Three new DB-level regression tests in test_authz_models.py:
- test_authz_role_assignment_blocks_duplicate_global
- test_authz_role_assignment_blocks_duplicate_scoped
- test_authz_role_assignment_allows_distinct_workspaces

* test(lfx): remove redundant @pytest.mark.asyncio decorators

src/lfx/pyproject.toml has asyncio_mode = "auto", so pytest-asyncio
auto-detects async test functions. The decorators on the seven async tests in
test_default_authorization_service.py are no-ops. Removed for clarity; tests
continue to pass via auto-detect (mode=Mode.AUTO confirmed in pytest output).

import pytest stays for the @pytest.fixture decorator on `service`.

* test(authz): complete DeploymentAction enum coverage in test_actions

test_deployment_action_values_match_casbin_strings only asserted READ, CREATE,
EXECUTE — WRITE and DELETE were missing despite being members of the enum.
Mirrored the FlowAction test structure: full value coverage plus a new
test_deployment_action_is_iterable_and_complete that enumerates members.
DeploymentAction has no DEPLOY (deploy is a flow-level action that mints a
deployment), so the expected set is five values.

* fix(authz): partial unique indexes on authz_share for NULL target_id

The original UNIQUE(resource_type, resource_id, scope, target_id) constraint
on authz_share did not prevent duplicate PRIVATE/PUBLIC shares because
target_id is NULL for those scopes and SQL treats NULL values as never-equal.

Replaces the constraint with two partial unique indexes:
- uq_authz_share_targeted: unique on (resource_type, resource_id, scope,
  target_id) WHERE target_id IS NOT NULL — covers TEAM/USER scopes.
- uq_authz_share_untargeted: unique on (resource_type, resource_id, scope)
  WHERE target_id IS NULL — covers PRIVATE/PUBLIC scopes without relying on
  NULL equality.

Chose partial indexes over CodeRabbit's sentinel-UUID suggestion to keep
target_id genuinely nullable (sentinel values leak into every read path that
filters by target_id, and any future FK on target_id would break).
Consistent with the d9e8f7a6b5c4 fix for authz_role_assignment.

New Alembic migration (Phase: EXPAND, validator: ) drops the broken
constraint via batch_alter_table and creates both partial indexes.

Three new DB-level regression tests in test_authz_models.py:
- test_authz_share_blocks_duplicate_targeted (USER scope dup → IntegrityError)
- test_authz_share_blocks_duplicate_untargeted (PUBLIC dup → IntegrityError)
- test_authz_share_allows_distinct_targets (sanity: different users OK)

* fix(authz): prefer project over workspace in _resolve_flow_domain

Casbin g2 inheritance is directional: a child resource inherits from its
parent, not the other way round. Declaring `g2, project:xyz, workspace:abc`
lets a workspace-scoped grant flow down to checks made against project:xyz,
but a project-scoped grant does NOT flow up to checks made against
workspace:abc.

The original precedence picked workspace whenever it was set, which made
project-scoped grants invisible to the enforcer the moment a workspace was
also known. Flip the precedence so the more specific domain (project) wins —
project-scoped grants now match directly, and workspace-scoped grants still
match via g2 inheritance. Both ids stay in the enforce context for ABAC.

- _resolve_flow_domain returns project first, then workspace, then "*".
- Docstrings on both _resolve_flow_domain and its callers updated.
- test_ensure_flow_permission_workspace_beats_project renamed to
  test_ensure_flow_permission_project_beats_workspace with inverted assertion
  and an explanation of g2 directionality.
- test_resolve_flow_domain_precedence updated accordingly.

Deployment helper uses the same function but only one deployment test passes
both ids; deployment behavior is unchanged in tests that pass project_id only.

* feat(authz): Phase 2 — list filtering, execute guards, project routes (#13255)

* feat(authz): Phase 2 — list filtering, execute guards, project routes

Extends the Phase 1 authorization foundations (#13153) with the route coverage
the IBM RBAC design note flags as "Not hooked yet" under §4.1 and the Phase 2
deliverables under §5.3 (workspace/project domain, list/filter APIs).

List filtering
- GET /flows/ now calls filter_visible_resources(FlowAction.READ) on the loaded
  list. OSS pass-through returns the input unchanged; the enterprise plugin
  uses batch_enforce to honor role + share grants.
- GET /projects/ applies the same filter against ProjectAction.READ.

Execute guards (FlowAction.EXECUTE)
- POST /build/{flow_id}/flow (chat.py): authorizes after loading the flow row.
- POST /run/{flow_id_or_name} (endpoints.py, api-key auth).
- POST /run/session/{flow_id_or_name} (endpoints.py, session auth).
- POST /webhook/{flow_id_or_name} (endpoints.py, webhook auth).

Project (folder) authorization
- New ProjectAction enum (READ/WRITE/CREATE/DELETE).
- New ensure_project_permission helper with workspace domain + owner override,
  mirroring the flow/deployment helpers.
- Guards wired into create_project, read_project, update_project,
  delete_project, download_file, upload_file.

Tests
- 7 new ensure_project_permission tests in test_utils.py (enum coercion,
  workspace domain, owner override, wildcard create object).
- 4 new test_execute_surfaces_guard_with_flow_execute parametrized cases.
- 6 new test_project_routes_guarded parametrized cases.
- test_read_flows_list_unchanged_in_this_pr renamed to
  test_read_flows_list_uses_filter_visible_resources; it now asserts the
  filter is called exactly once.

Docs
- Added an "Authorization (RBAC)" section to AGENTS.md describing the plugin
  model, route helpers, Casbin request shape, and the dry-run CLI.

Out of scope for Phase 2 (per design note §5.3):
- Knowledge bases and global variables (Phase 4: deployments, components,
  secrets, audit UI).
- Deployment list filtering (paginated + shaped items; needs SQL-level
  prefiltering once authz_share lands in Phase 3).
- authz_share CRUD APIs and UI (Phase 3 — required for full RBAC).

* docs(authz): correct domain precedence note in AGENTS.md

Follows the upstream _resolve_flow_domain fix on the parent branch — project
wins over workspace so project-scoped grants match directly and workspace
grants still flow down via Casbin g2.

* fix(authz): align dry-run CLI with project-over-workspace domain precedence

The _resolve_flow_domain flip (project wins over workspace, ac3e8bcf0f) made
test_domain_is_workspace_prefixed unsatisfiable — every dry-run scenario passes
both workspace_id and folder_id, so every audited row now resolves to
project:{folder_id}.

- Renamed the test to test_domain_is_project_prefixed and updated the assertion.
- Updated the matching fallback literal in authz_dry_run.py so it reflects
  what the resolver would actually emit (the fallback fires only when no audit
  row is written, which is rare since AUTHZ_ENABLED is forced True in dry-run).

* fix(authz): block-merge fixes — note_translations leak, schema hardening

Eight fixes from the post-merge review of #13153:

C1 security — GET /flows/{flow_id}/note_translations leaked note text across
users. The handler authenticated the caller but never scoped the fetch or
called ensure_flow_permission, so any authenticated user could read note
content from any flow by guessing the UUID. Switch to _read_flow (owner-scoped)
and add ensure_flow_permission(READ); return {} on miss so missing-flow vs
not-owned vs not-authorized are indistinguishable.

C2 database — Casbin's loader filters by ptype on every load_policy() and
AddPolicy(). Without an index, every enterprise policy refresh became a full
table scan on casbin_rule. Add ix_casbin_rule_ptype.

C3 database — Every authz timestamp column was created as sa.DateTime() with
no timezone. Inconsistent with deployment/api_key/variable models, and on
Postgres it strips tzinfo on write so audit-log ordering can drift across DST.
Switch to DateTime(timezone=True) on every authz timestamp; migration uses
postgresql_using="column AT TIME ZONE 'UTC'" to convert existing rows safely.

H1 security — uq_authz_role_assignment_global filtered on
(domain_type='global' AND domain_id IS NULL). A row with domain_type='org'
AND domain_id IS NULL was covered by neither partial index and duplicates
slipped through. Widen the filter to domain_id IS NULL (drop the domain_type
check); rename the index to uq_authz_role_assignment_unscoped to reflect that
it covers every NULL-domain_id case.

H2 database — authz_share.created_by was ondelete=CASCADE. Deleting a user
silently revoked every share they created. Mirror the SET NULL pattern from
authz_audit_log.user_id and make the column nullable.

H3 database — "list all shares for resource X" runs WHERE resource_type=? AND
resource_id=? — bitmap-AND of two single-column indexes works but a composite
index is tighter and matches the precedent set by ix_authz_audit_log_resource.
Add ix_authz_share_resource (resource_type, resource_id).

H4 python — noqa: TC002 suppressions on factory.py runtime imports were
hiding a real ruff finding: with `from __future__ import annotations`, those
imports are typecheck-only. Move them into the TYPE_CHECKING block.

H5 python — _authz_settings had no return type annotation. Mypy inferred Any
which propagated through is_enabled() and every downstream consumer. Add the
explicit AuthSettings return type.

Plus a TODO comment on AuthzAuditLog noting the retention/partitioning gap
flagged by the DB reviewer (M7 in the synthesis).

New regression tests:
- test_authz_role_assignment_blocks_duplicate_non_global_with_null_domain_id
- test_get_note_translations_is_owner_scoped_and_guarded

112 authz tests pass; migration validator output Valid: ; alembic upgrade
head runs clean against a fresh SQLite DB.

* refactor(authz): collapse duplicated ensure_*_permission helpers

The three public guards (flow/deployment/project) shared ~130 LOC of
copy-paste: object key assembly, action coercion, owner-override check, audit
write, and delegation to ensure_permission. Two changes from the Python
reviewer (M1, M2 in the synthesis):

- Extract a private ``_ensure_resource_permission`` core that owns the
  identical structural pattern. The three public helpers become thin wrappers
  that build the resource-specific extra_context (with the right key names —
  ``flow_user_id`` vs ``deployment_user_id`` vs ``project_user_id``) and
  delegate. Adding a fourth resource type is now a five-line addition instead
  of a 45-line copy.

- Rename ``_resolve_flow_domain`` to ``_resolve_casbin_domain`` because it
  serves flows AND deployments (callers pass ``folder_id`` or ``project_id``
  through the same helper). Parameter renamed from ``folder_id`` to
  ``scope_id`` to reflect the polymorphic role. A module-level alias
  ``_resolve_flow_domain = _resolve_casbin_domain`` preserves the old name
  for downstream importers; the test asserts both names resolve to the same
  function.

Drive-by: extracted ``_OWNER_CONTEXT_KEYS`` constant so the audit-detail
extraction picks up ``project_user_id`` too (previously only
``flow_user_id`` and ``deployment_user_id`` were forwarded to audit rows).

Skipped the L6 finding (collapse pass-through ``enforce``/``batch_enforce``
into ``BaseAuthorizationService``). Keeping those abstract on the base is a
deliberate safety guard — an enterprise plugin that forgets to override
``enforce`` should fail loudly at instantiation, not silently inherit
allow-all in production. The duplication between LangflowAuthorizationService
and the lfx default is the price.

112 authz tests pass; ruff clean.

* fix(authz): remove redundant owner-only check + per-domain list filter + share CHECK constraints

Three fixes from the post-merge static review:

1. ``check_flow_user_permission`` rejected any flow where
   ``flow.user_id != api_key_user.id`` — fine for OSS pass-through but a real
   regression under enterprise enforcement, because an execute-grant on a
   shared flow would still be blocked here even after the route's upstream
   ``ensure_flow_permission(FlowAction.EXECUTE, ...)`` passed. Remove the
   helper entirely; route-level authorization is the only gate now. Added the
   missing ``ensure_flow_permission(EXECUTE)`` call to ``experimental_run_flow``
   so the contract holds: every ``_run_flow_internal`` caller authorizes
   upstream. New ``test_check_flow_user_permission_is_gone`` regression
   prevents a re-introduction.

2. ``filter_visible_resources`` sent a single ``domain`` for the entire batch,
   so candidates living in different workspaces/projects all evaluated against
   the same Casbin tuple — project-scoped grants became invisible. Added an
   optional ``domain_extractor`` parameter that groups candidates by their
   resolved domain and calls ``batch_enforce`` once per group. Output order is
   preserved. ``GET /flows/`` and ``GET /projects/`` now pass extractors that
   call ``_resolve_casbin_domain(item.workspace_id, item.folder_id)``. New
   ``test_filter_visible_resources_groups_by_extracted_domain`` covers the
   per-domain dispatch.

3. ``authz_share.scope`` and ``permission_level`` were free-text columns —
   typos like ``scope='PRIVATE'`` would silently bypass the partial unique
   indexes (which match the lowercase form), and rows could be inserted with
   ``scope='public'`` + a ``target_id`` (mismatched targeted/untargeted
   semantics). Added three CHECK constraints:
   - ``ck_authz_share_scope_enum``
   - ``ck_authz_share_permission_enum``
   - ``ck_authz_share_scope_target_consistency`` (TEAM/USER → target required;
     PRIVATE/PUBLIC → target forbidden)
   Three new DB-level regression tests.

Phase 3 prerequisite documented (not fixed):
- ``get_flow_for_api_key_user`` and other owner-scoped lookup helpers still
  filter by ``user_id``, so enterprise execute-grants on flows the caller
  doesn't own would 404 at the lookup before the upstream
  ``ensure_flow_permission`` can permit them. The proper fix is share-aware
  fetch helpers that land alongside ``authz_share`` CRUD in Phase 3.

Out-of-scope (skipped with reason):
- ``FlowAction.DEPLOY`` enum value remains unwired. Design note §5.1 plans it
  for WxO publish on flow-version attach; wiring it on every deployment
  create/update is Phase 4 work.

118 authz tests pass; ruff clean; migration validator output Valid: ;
alembic upgrade head runs clean against a fresh SQLite DB.

* refactor(authz): collapse six pending migrations into one consolidated file

None of the authz migrations have shipped yet — this branch is the first PR
introducing them. The chain had grown to six files as findings landed:

  mb01b2c3d4e5
   └─ f7a8b9c0d1e2 (initial tables)
       └─ c8e5f4b2a9d7 (workspace_id columns)
           └─ d9e8f7a6b5c4 (partial unique indexes on role_assignment)
               └─ e4f5a6b7c8d9 (partial unique indexes on share)
                   └─ f0a1b2c3d4e5 (tz timestamps, ptype index, FK, etc.)
                       └─ b2c3d4e5f6a1 (CHECK constraints on share)

Replace all six with a single 7c8d9e0f1a2b migration that emits the final
schema directly. New tip: mb01b2c3d4e5 → 7c8d9e0f1a2b.

Drive-by model fixes uncovered by test_model_migration_consistency:
- _tz_column helper now accepts index=True so the audit log timestamp
  column carries its index (lost in the earlier helper extraction).
- Aligned unique-index vs unique-constraint emission for the three tables
  whose models use Field(index=True, unique=True) — emit a single unique
  index, not a separate UniqueConstraint, so autogen sees no drift.
- Renamed CHECK constraint base names from ck_authz_share_* to * — the
  alembic naming convention prepends ck_<table>_ automatically, so the
  previous names were getting doubled. Final names are now correct
  (ck_authz_share_scope_enum, etc.).

149 authz + alembic tests pass; ruff clean; migration validator output
Valid: ; alembic upgrade head runs clean against a fresh SQLite DB.

* fix: merge

* [autofix.ci] apply automated fixes

* Update Structured Data Analysis Agent.json

* fix(authz): narrow Phase 1 claims + filter paginated /flows/ branch

Two findings from the latest static review:

P1 — Owner-scoped fetch shadows enterprise grants

Route guards (ensure_flow_permission, ensure_deployment_permission,
ensure_project_permission) sit on top of fetch helpers that still scope
queries by current_user.id. So even with an enterprise plugin registered, a
share grant on a non-owned flow / folder / deployment still 404s at the
fetch layer before the guard can authorize. Cross-user reachability is
genuinely Phase 3 work (needs share-aware fetch helpers that load by id
first and convert plugin denies to 404 for UUID-privacy).

Rather than half-fix it, narrow the Phase 1 claims and document the gap
where future readers will see it:

* Module-level docstring on services/authorization/utils.py spelling out
  the Phase 1 contract and why cross-user enforcement is not yet reachable.
* Inline "Phase 3 prerequisite" markers at the five lookup sites the
  review flagged: flows.py:read_flow, chat.py:build_flow, projects.py:read_project,
  endpoints.py:get_flow_for_api_key_user (already had one), and
  mappers/deployments/helpers.py:get_deployment_row_or_404.
* AGENTS.md authz section now explicitly calls out the owner-scoped-fetch
  limitation.
* PR description updated to scope the deliverables to Phases 1+2 and call
  out Phase 3 prerequisites explicitly.

P2 — Paginated /flows/ branch skipped filter_visible_resources

read_flows had two return paths: get_all=True ran filter_visible_resources;
get_all=False (the paginated branch) returned apaginate result raw. So one
public mode of the same endpoint silently sidestepped authz filtering.

The fix wires filter_visible_resources into the paginated branch using the
same domain_extractor. OSS pass-through is a no-op so behavior under the
default flag is unchanged. page.total may overcount denied items under
enterprise enforcement — accurate paginated counts require SQL-level
prefiltering (Phase 3, alongside authz_share).

test_read_flows_list_uses_filter_visible_resources tightened to assert TWO
filter_visible_resources calls (one per branch) so the next regression
where someone forgets one path is caught immediately.

149 authz + alembic tests pass; ruff clean.

* test(authz): add user_id + workspace_id to _fake_deployment_row fixture

ensure_deployment_permission, wired into every guarded deployment handler,
reads deployment_row.user_id (for owner override) and
deployment_row.workspace_id (for domain resolution). The unit-test fixture
``_fake_deployment_row`` predates the authz hooks and didn't populate either
field, causing AttributeError on any test path that flows through a guarded
handler — surfaced by TestUpdateDeploymentRollback.test_rollback_called_on_commit_failure.

Add both fields with sensible defaults (random UUID for user_id matching the
production model, None for workspace_id which resolves to the wildcard domain).
Override-friendly so individual tests can pin specific values.

* fix(authz): tighten flow upload, project download, deployment list guards

Three guard placements were misshapen — coarse where they should be specific,
specific where they should be coarse, or missing context the resolver needs.

P1-1 — flows.py upload_file: remove the pre-parse coarse CREATE check

The handler runs ``ensure_flow_permission(CREATE, folder_id=folder_id)``
before the file is parsed, then runs a per-flow CREATE check after parsing
with the actual ``workspace_id``/``folder_id`` each uploaded flow targets.
The pre-parse check evaluates against ``domain="*", obj="flow:*"`` (or
``project:{folder_id}`` if the optional query param is set), which can
reject a caller who has create permission only inside the project/workspace
the uploaded flows actually target. The per-flow check is the right
enforcement point; drop the pre-parse check entirely.

P1-2 — projects.py download_file: pass owner + workspace into the guard

``ensure_project_permission(ProjectAction.READ, project_id=project_id)``
without ``project_user_id`` or ``workspace_id`` skips the owner-override
short-circuit AND resolves to the wildcard domain. An owner-only role in
the enterprise plugin would then deny the project's own owner; a
workspace-scoped role would never match. Fetch the project row first,
then pass the resolved owner and workspace.

P2-1 — deployments.py list_deployments: filter rows per item

The list endpoint had a single coarse ``deployment:*`` READ check and no
per-row filtering. That forced operators to grant global read to anyone
who should see any deployments — even those scoped to a single project.
Apply ``filter_visible_resources`` on ``rows_with_counts`` before
shaping, with a per-row ``domain_extractor`` so each deployment is
evaluated at ``project:{project_id}``/``workspace:{workspace_id}``. OSS
pass-through returns the input unchanged. ``total`` still reflects the
raw DB count — accurate paginated totals need SQL-level prefilter
(Phase 3, alongside ``authz_share``).

P2-2 (open question) — services/utils.py registration: docstring only

Confirms the OSS default registers with ``override=True`` (same as
``auth_service``) and that enterprise plugins are expected to override via
``lfx.toml`` rather than entry points. Plain entry-point registration uses
``override=False`` in the LFX manager and would lose to the OSS default —
not a bug but worth being explicit so enterprise integrators don't pick
the wrong mechanism.

83 authz + deployment-handler unit tests pass; ruff clean.

* fix(authz): guard deprecated build routes, owner-override list filter, persist workspace_id

Three findings from the latest review.

P1-1 — Deprecated build routes bypassed authorization

POST /build/{flow_id}/vertices (chat.py retrieve_vertices_order) and
POST /build/{flow_id}/vertices/{vertex_id} (chat.py build_vertex) are
hidden from the OpenAPI schema (deprecated=True, include_in_schema=False)
but still routed. Both eventually call build_graph_from_db, which does a
raw session.get(Flow, flow_id) in flow_utils.py with no owner filter, so
any authenticated user could build any other user's flow by guessing a
UUID. Apply the same owner-or-public fetch + ensure_flow_permission(EXECUTE)
the supported build_flow uses. retrieve_vertices_order now binds
current_user explicitly (was previously a dependencies-only declaration).

P1-2 — List filtering hid resources from their owners

filter_visible_resources called batch_enforce with only user context and
no per-candidate owner context. Direct guards in _ensure_resource_permission
short-circuit when owner_id == user.id and force-allow without calling
enforce. The list path did not — so a deny-all enterprise policy would
hide a user's own rows from the listing while the same user could read
each row directly. Made list and direct read disagree.

Added optional owner_extractor parameter. Items whose owner UUID equals
user.id are force-included before the enforcer is consulted; the
enforcer only sees the items that need a real decision. All three list
callers (flows GET /, projects GET /, deployments GET /) pass
``owner_extractor=lambda item: item.user_id`` (or the tuple-aware
equivalent for deployments).

New regression test asserts owner-override path bypasses a deny-all stub.

P2 — workspace_id update silently dropped

FlowUpdate accepts workspace_id and the PATCH/PUT handlers re-authorize
WRITE at the destination scope when the payload changes it. But
_UPDATABLE_FLOW_FIELDS in flows_helpers.py omitted workspace_id, so the
update passed authz, returned 200, and the row stayed in its old
workspace. Add workspace_id to the allowlist so the move actually
persists.

86 authz tests pass; ruff clean.

* test(authz): align deprecated build-route tests with 404 contract + cross-user behavioral coverage

P1 — existing tests asserted 500 for unknown flow ids on the deprecated
/build/{flow_id}/vertices and /build/{flow_id}/vertices/{vertex_id} routes.
The new ownership check returns 404 (matches /build/{flow_id}/flow and
preserves UUID-privacy). Update both assertions and document why.

P2 — the existing AST-level guard test only proves the route SOURCE contains
an ensure_flow_permission call; it does not exercise behavior. Add two
integration tests that:

  1. Create a flow owned by the conftest's active_user.
  2. Log in as a second, distinct user.
  3. Hit /build/{flow_id}/vertices and /build/{flow_id}/vertices/{vertex_id}
     with the second user's bearer token.
  4. Assert 404 — the same opaque response another user's nonexistent flow
     would produce.

The second-user fixture lives inline in this test file since the project's
conftest only ships single-user infrastructure (active_user /
active_super_user).

* feat: Phase 3 and 4 implementations for RBAC

* fixes from round of review

* Round 2 review fixes

* fix: Next round of review fixes

* fix: More hardening of KBs in RBAC

* fix: Third round of review feedback

* fix: Fourth round of review updates

* Update deployments.py

* Few more deployment tightens

* Update deployments.py

* Update test_deployment_route_handlers.py

* Update test_s3_endpoints.py

* [autofix.ci] apply automated fixes

* fix: Route guards and unit tests

* fix: filter paginated branch of read_project

* Update auth.py

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

* docs(authz): shorten docstrings and drop enterprise/Casbin comments

Use one-line module and helper docstrings across authorization code and
route guards; keep table/type identifiers unchanged.

* refactor(db): consolidate authz migrations into a single revision

Merge system-role seeding into 7c8d9e0f1a2b and drop 8d3a1f9c2e0b.

* fix: audit log retention policy and cleanup

* refactor(authz): split utils.py and address PR #13153 review

Address review feedback on the OSS authorization foundations PR:

- B1/I1: Split services/authorization/utils.py (694 lines, mixed
  responsibilities) into focused modules:
    - audit.py    — batched audit pipeline (audit_decision, writer loop)
    - guards.py   — ensure_*_permission family (collapsed via _RESOURCE_SPECS
                    registry; nine 25-line clones become 7 thin wrappers)
    - listing.py  — filter_visible_resources
  utils.py remains as a back-compat re-export shim so existing call sites
  (api/v1/flows.py, deployments.py, etc.) keep working untouched.

- B2: Split tests/unit/services/authorization/test_utils.py (1004 lines)
  into test_audit.py, test_guards.py, test_filter_visible.py,
  test_domain_resolution.py. Shared stubs + monkeypatch helpers extracted
  into _common.py; pytest fixtures into conftest.py.

- I2: ensure_permission default 403 detail is now "Permission denied" —
  callers can opt into a richer message via the new `detail=` kwarg. The
  previous default echoed flow:<uuid> in the response, leaking existence
  to non-owners on any route that forgot to wrap in deny_to_404.

- I4: Document the OSS floor contract on _ensure_can_administer_share —
  the early-return is dead in OSS (supports_cross_user_fetch() is False)
  and the explicit 403 is the actual floor; the enterprise plugin gates
  via the downstream ensure_share_permission call.

- R2: Audit drop logging is now time-based (first drop + at most every
  10s during persistent saturation) instead of every 1000th drop. Low
  drop rates no longer go minutes without a log line.

- R5: Seed migration's non-Postgres/SQLite fallback wraps the INSERT in
  a SAVEPOINT and swallows IntegrityError. Two concurrent migration
  runners can no longer race past the SELECT-then-INSERT check.

- R6: IntegrityError tests in test_authz_models.py now verify the
  *specific* constraint fired — column names for unique partial indexes
  (SQLite doesn't surface the index name), constraint name substring
  for CHECK constraints. A generic NOT NULL regression would no longer
  pass these tests.

All 180 authz tests pass; ruff clean.

* fix: One more review comments pass

* fix: remove dry run script

* fix(db): chain authz migration after api_key expires_at revision

Both 7c8d9e0f1a2b and f6b3ce6845d4 pointed at mb01b2c3d4e5, causing
MultipleHeads on startup. Authz now revises f6b3ce6845d4.

---------

Co-authored-by: Eric Hare <ericrhare@gmail.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-05-27 10:36:39 -07:00
2d375b2784 Merge release-1.9.4 into release-1.10.0 2026-05-27 08:49:11 -07:00
e248f4f854 fix(api-keys): add expiration date support for API keys (#13330)
* Added is_active check to API Keys, expiresAt field that tracks API Key expiry, UI elements to surface this to the Langflow user.

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-05-27 01:46:59 +00:00
8e5dd71c0f fix(tracing): isolate Langfuse OTel tracer to stop HTTP span pollution (#13344)
* fix(tracing): isolate Langfuse OTel tracer to stop HTTP span pollution

Pass an explicit `TracerProvider` to `Langfuse(...)` so the SDK does not
register itself as the global OpenTelemetry tracer provider. Without this,
`FastAPIInstrumentor.instrument_app` (which uses the global provider)
exports every HTTP request span to Langfuse, flooding traces with health
checks, flow list calls, and other unrelated routes.

Adds regression tests verifying:
- the shared Langfuse client is constructed with an explicit
  `tracer_provider`, and
- initializing the client does not swap out the process-wide OTel
  `TracerProvider`.

Fixes #13319

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-05-27 01:15:24 +00:00
6901cf68e9 fix(mcp): fix validation, session isolation, and update_flow_from_spec parity (#12528)
* fix(mcp): fix validation, session isolation, and update_flow_from_spec parity

- create_flow_from_spec now calls validate_flow (polls for completion)
  instead of fire-and-forget build_flow
- validate_flow filters poll results by job_id to avoid stale builds
- update_flow_from_spec uses server-side tools (configure_component,
  connect_components) for feature parity with create_flow_from_spec,
  with rollback on failure
- login() only closes the current session's client and registry,
  no longer disrupts other concurrent SSE sessions
- _set_client invalidates _shared_registry when the client changes
  to prevent stale registries after server switch
- validate_flow returns consistent "errors" list format

* test(mcp): add unit tests for validation, session isolation, and rollback fixes

* test(mcp): remove obsolete validate_flow job_id polling tests

The polling-by-job_id approach was superseded by streaming validation
in release-1.9.0 (event_delivery=direct). Remove the 4 tests that
exercised the removed polling code path.

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

* fix(mcp): coerce primitive config values to declared field types

create_flow_from_spec parses YAML config blocks, so numeric / boolean
inputs naturally land as int / float / bool primitives. The flat
configure_component path wrote those into the template as-is and the
downstream build then choked: e.g. ChatInput.input_value is a str-typed
MultilineInput, but YAML 'A.input_value: 42' delivered int 42 and the
Message constructor refused ("The message does not have the required
fields (text, sender, sender_name).") -> RuntimeError 'Flow validation
failed: Build error' rolled the whole spec back.

Adds _coerce_param_value that reads each field's declared 'type' from
the template and coerces str/int/float/bool primitives accordingly.
Containers, code, and unknown shapes pass through unchanged so existing
non-primitive call sites are unaffected. None passes through too --
clearing a field is a valid intent.

bool->str converts to lowercase 'true'/'false' to match the YAML spec
convention (str(True) would give 'True'/'False' capitalized).

Test test_create_flow_from_spec_coerces_numeric_config now passes; the
assertion was tightened to compare against the coerced string form
"42" since the coercion target for input_value is str.

Pre-existing test_create_flow_from_spec_with_tool_mode failure (the
Agent component requires a model + API key to build) is unrelated to
this change -- it fails the same way against the parent commit and is
left for a separate fix.

* [autofix.ci] apply automated fixes

* test(mcp): skip build validation in tool_mode wiring test

Agent requires a model + API key to build, but this test only verifies
that URLComponent.component_as_tool -> Agent.tools wiring round-trips
through the spec parser. Pass validate=False so the test no longer
depends on a buildable graph.

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-05-26 23:47:55 +00:00
aea4dd05be fix(server): refuse to start with multiple workers and the default in-memory job queue (#13202)
* fix(server): refuse to start with multiple workers and the default in-memory job queue

The default JobQueueService keeps build queues in process-local memory.
With workers > 1, Gunicorn round-robins POST /api/v1/build/<flow>/flow and
the follow-up GET /api/v1/build/<job>/events across workers, so the
polling worker finds no queue and returns "Job queue not found for
job_id" roughly 45-66% of the time under load.

Refuse to start in that config rather than letting operators discover it
in production. The RuntimeError lists the two operator-actionable
workarounds (configure a Redis-backed shared queue, or run --workers 1)
and notes that event_delivery=direct also works since the POST endpoint
streams events back inline on the same worker — but the server cannot
enforce that at startup because it does not know which delivery mode
every future client will pick.

Only triggered on the Gunicorn (Linux) path; the direct-uvicorn path on
Windows and macOS already clamps workers to 1.

* fix(server): skip the multi-worker refusal when LANGFLOW_JOB_QUEUE_TYPE=redis

Addresses review feedback on PR #13202: the function listed the redis
queue as one of the operator-actionable workarounds but still refused
to start even when LANGFLOW_JOB_QUEUE_TYPE=redis was configured, so the
workaround was undocumented broken.

ensure_multi_worker_safe now reads settings.job_queue_type and returns
silently when redis is configured. Redis-backed queues share state
across workers and support every event_delivery mode, so the race the
check guards against cannot occur.

Add a positive test covering the redis case; switch the existing
refusal tests to patch the settings service explicitly so they are
deterministic regardless of the surrounding environment.
2026-05-26 23:25:34 +00:00
61baba9499 fix(traces): bypass impl result_processor in _LegacyCaseEnum so legacy uppercase rows load (#13346)
* fix(traces): bypass impl result_processor in _LegacyCaseEnum to load legacy uppercase rows

The previous attempt at normalising legacy uppercase enum values failed
because ``TypeDecorator.result_processor`` chains the impl's
``result_processor`` *before* ``process_result_value``:

    impl (SQLEnum) -> process_result_value

SQLEnum's processor calls ``_object_value_for_elem`` which raises
``LookupError`` for any raw DB string not in the lowercase values list.
Pre-v1.9.2 trace/span rows persist the enum *names* (``'OK'``,
``'ERROR'``, ``'CHAIN'``...), so the impl's processor blew up before
``_LegacyCaseEnum.process_result_value`` had a chance to normalise the
value. The traces panel hit this on every read, returning 500 from the
backend with ``LookupError: 'OK' is not among the defined enum values``.

Override ``result_processor`` to skip the impl's processor entirely and
run only ``process_result_value`` against the raw DB string. Old rows
(uppercase names) and new rows (lowercase values) both round-trip
cleanly.

Adds an end-to-end ORM regression test that seeds a row through the
ORM, rewrites the enum column to the legacy uppercase form via raw SQL,
and asserts the ORM SELECT decodes it back to the right enum member.
The existing ``TestLegacyCaseEnumResultProcessor`` tests called
``process_result_value`` directly and missed this bug — the new tests
exercise the full SQLAlchemy result_processor pipeline.

Fixes #13318

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-05-26 21:47:39 +00:00
27ba63c1d7 fix: set graph inputs by component type (#13343)
* fix: set graph inputs by component type

Fixes #10796

Co-authored-by: Yuri Chukhlib <yuri.v.chu@gmail.com>

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

---------

Co-authored-by: Yuri Chukhlib <yuri.v.chu@gmail.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-05-26 21:01:40 +00:00
b95d309c46 fix(tracing): surface Langfuse setup failures, pin pydantic>=2.13 (Py3.14) (#13341)
* fix(tracing): surface Langfuse setup failures and pin pydantic>=2.13 for Py3.14

Docker v1.9.3 silently dropped Langfuse traces because the Docker image
was bumped to Python 3.14 while the lockfile still resolved pydantic
2.12.x. Langfuse v3 imports `pydantic.v1.BaseModel`, which only gained
Python 3.14 support in pydantic 2.13. On the user's Docker container,
`from langfuse import Langfuse` raised `pydantic.v1.errors.ConfigError`,
the broad `except Exception` in `_setup_langfuse` logged at debug level,
and the tracer initialized with `_ready = False` — no error in default
logs, no traces in Langfuse. PyPI installs worked because users tend to
run Python 3.10-3.13 where pydantic.v1 is still happy.

Two changes:

- Replace `logger.debug` with `logger.warning`/`logger.exception` in
  `_setup_langfuse` so future failures (network, auth, dependency
  conflicts) surface in logs by default instead of vanishing silently.
- Add `pydantic>=2.13.0` to the `langfuse` extra in
  `src/backend/base/pyproject.toml` so the Python 3.14 import path is
  guaranteed to work whenever the extra is installed, regardless of
  what other deps resolve transitively.

Adds regression tests asserting `_setup_langfuse` calls
`logger.warning`/`logger.exception` on the three failure modes
(auth check returning False, auth check raising, post-auth setup
exception).

Fixes #13317

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-05-26 20:31:44 +00:00
e24b265482 fix(frontend): Safari Selected Components (#13283)
* refactor(frontend): rename useShiftDragSelectFix → useCanvasDragSelectFix

* add testcases

* ruff changes

---------

Co-authored-by: Olayinka Adelakun <olayinkaadelakun@mac.war.can.ibm.com>
Co-authored-by: Viktor Avelino <64113566+viktoravelino@users.noreply.github.com>
Co-authored-by: Olayinka Adelakun <olayinkaadelakun@Olayinkas-MacBook-Pro.local>
2026-05-26 19:23:30 +00:00
1e138141e0 feat: LE-1017 Event bus for bundle events (#13253)
* Adding lfx bundle lifecycle events.

Routed the useExtensionService to poll for bundle extension updates.

Added reload event deduplication to bundles.

Make bundle events user scoped. Aligned bundle schemas between BE and FE.

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-05-26 18:51:44 +00:00
06666736ca feat: add DB migration validation workflow (LE-1259) [backport 1.9.4] (#13348)
* feat: Add DB migration validation workflow for nightly builds (LE-1259)

- Implements automated DB migration testing for nightly builds
- Tests two scenarios: pip/venv and Docker Compose migrations
- Validates migration from stable to nightly versions
- Verifies data persistence (witness flows) across migrations
- Integrated into nightly_build.yml workflow
- Includes Slack notifications for migration test results

This addresses the critical blocker identified by QA team for ensuring
safe database migrations in production deployments.

* docs: Add DB migration validation documentation

- Comprehensive guide for LE-1259 implementation
- Detailed test scenarios and execution details
- Environment configuration and success criteria
- Monitoring and troubleshooting guidelines
- Placed in docs/docs/Deployment/ for easy access

* fix: address PR review comments for DB migration validation

- Use actual nightly tag from create-nightly-tag output instead of hardcoded :latest
- Wire POSTGRES_VERSION env var into postgres service image tag
- Remove unnecessary checkout steps from both migration jobs
- Update step name from 'Create witness flow and credentials' to 'Create witness flow'
- Add -f flag to curl commands for fail-fast behavior
- Add flow creation verification with error handling
- Fix version extraction logic to properly test nightly build instead of PyPI latest
- Remove deprecated docker-compose version field
- Remove duplicate Slack notification job (consolidated in nightly_build.yml)

Addresses all 9 issues identified by @ogabrielluiz in PR review

* docs: remove implementation summary from user-facing docs

Per @ogabrielluiz review feedback, this file reads as an implementation
summary (Jira ticket, branch name, 'Next Steps', 'Files Changed: 2')
rather than user-facing documentation. The Deployment section is for
end-user docs, and this content is better suited for the PR description.

Also not added to sidebars.js, so would be an orphan page.

* fix(workflows): address 6 issues from Gabriel's second review of DB migration validation

Fixes all remaining issues identified in PR #13249 review:

1. Remove schedule trigger - only works on default branch, would cause duplicate runs
2. Fix postgres service image - hardcode to postgres:16 (env context not available in services)
3. Add curl fail-fast flags - use -fsSL for immediate failure on errors
4. Add flow ID verification - check witness data creation succeeded before proceeding
5. Remove orphaned Slack JSON - cleanup leftover from removed notify-results job
6. Fix version extraction - strip 'v' prefix for pip install (${VERSION#v})
7. Fix Docker image reference in nightly_build.yml - pass full image path with tag
8. Fix success notification - check migration validation didn't fail

All changes validated locally:
- YAML syntax validation passed
- Docker Compose config validated
- Tag manipulation logic tested (v prefix handling)
- Curl command structure verified
- Test credentials marked with pragma allowlist secret comments

Related: LE-1259, PR #13249
Depends on: PR #13212 (Docker volume permissions fix)
2026-05-26 14:00:02 -04:00
87e9c908c6 fix(playground): add gap between consecutive error cards in chat messages (#13325)
* fix(playground): add gap between consecutive error cards in chat messages

When a chat message carries more than one error ContentBlock (e.g. a
component or future agent emits multiple step-level errors per run),
ErrorView used to render the cards as sibling <div>s with no parent
flex / spacing utility, so they butted edge-to-edge and read as one
merged block.

Wrap the blocks.map output in `<div class="flex flex-col gap-2">` so
each card keeps an 8px breathing space. Matches the inter-card rhythm
used by IngestionRuns / IngestionHistory row stacks elsewhere in the
app. Single-block case is unchanged (gap utility no-ops with one
child).

Drive-bys (lines biome would re-flag because the file is touched):
  - Remove dead Markdown / remarkGfm imports and the unused
    ``errorMarkdownComponents`` constant (truly unreferenced).
  - Remove the dead ``CodeTabsComponent`` import and the unused
    ``ComponentPropsWithoutRef`` type import.

Adds ``__tests__/error-message.test.tsx`` covering: single block in
the gap-aware stack, three blocks each rendered as a direct child of
the stack (regression guard against wrapping that would defeat
``gap-2``), and loading state path that should not render the stack.

Fixtures include the required ``allow_markdown`` + ``component`` fields
on ``ContentBlock`` so ``tsc --noEmit`` stays clean. Locally re-mocks
``genericIconComponent`` to expose the ``ForwardedIconComponent`` named
export the SUT imports.

* test(playground): drop className assertions from error-message tests

Address review feedback on PR #13325:
  - viktoravelino: className-string expectations couple the test to
    Tailwind utilities that can change anytime and are not behavioural.
    Replace ``expect(stack.className).toContain("gap-2"|"flex"|...)``
    with the structural assertion that each error card is a *direct
    child* of the stack — that is the actual behavioural guarantee
    that lets any spacing rule (gap utility today, CSS module
    tomorrow) apply. A future regression wrapping cards in an extra
    div still trips this test without us encoding class strings.
  - CodeRabbit: remove the dead CodeTabsComponent mock; the import
    was removed from error-message.tsx so the mock had no consumer.

Describe rename: "multi-card spacing" -> "multi-card structure" to
match the new framing. 3/3 tests still green.
2026-05-26 17:56:13 +00:00
8452d41642 feat(memory): align Create Memory modal layout and spacing with Create Variable modal design system (#13287)
* feat(memory): align Create Memory modal layout and spacing with Create Variable modal design system

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes

* fix(memory): fix spacing, tag inconsistencies, required field markers, and scrollbar position in Create Memory modal

* fix(memory): fix spacing, tag inconsistencies, required field markers, and scrollbar position in Create Memory modal

---------

Co-authored-by: Olayinka Adelakun <olayinkaadelakun@mac.war.can.ibm.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Olayinka Adelakun <olayinkaadelakun@Olayinkas-MacBook-Pro.local>
2026-05-26 17:51:10 +00:00
75b3cc9f6d chore: upgrade uv.lock 2026-05-26 13:14:23 -04:00
b531f559d6 chore: port pre-create LANGFLOW_CONFIG_DIR #13212
backport of https://github.com/langflow-ai/langflow/pull/13212
2026-05-26 13:14:23 -04:00
c3ae41e6c7 fix(playground): hide session-row checkbox until hover or selection (#13314)
The selectable-row checkbox was always rendered at full opacity, adding
visual noise to every session in the sidebar. Hide it by default and
reveal it only when the row is hovered, while keeping checked boxes
permanently visible so users can still see what they have selected
without re-hovering each row.

Implementation:
  - Apply ``invisible group-hover:visible`` to the icon when the row is
    not selected; the row already carries the ``group`` class, so the
    utility resolves without further plumbing.
  - Skip those classes when ``isSelected`` so a checked box stays
    visible regardless of pointer location.
  - Keep the wrapping ``w-4 h-4`` column so the row layout does not
    jump on hover/unhover. ``visibility: hidden`` also disables pointer
    events so a stray click on the hidden column cannot toggle
    selection.
  - Add ``transition-opacity`` for a smooth reveal.

The Select All checkbox at the top of the multi-select region remains
always visible — it is the entry-point affordance for multi-select.

Adds ``__tests__/session-selector-checkbox-visibility.test.tsx`` (5
cases): hidden default + hover utility wiring, always-visible-on-
selected, layout-jump guard via reserved column, click toggles
selection without bubbling to row toggleVisibility, and showCheckbox
false renders nothing.
2026-05-26 17:02:00 +00:00
87039b46d3 fix: port chroma collection hardening (#13339)
* fix: port chroma collection hardening

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-05-26 08:08:19 -07:00
01cd7dc88f chore: remove dup doc line 2026-05-26 10:59:31 -04:00
26736d6d7d fix: align diskcache removal port 2026-05-26 10:59:31 -04:00
0a5f25f32b chore: port Remove the diskcache dependency
port of https://github.com/langflow-ai/langflow/pull/12953
2026-05-26 10:59:31 -04:00
22b17d1640 feat(memories): add in-product docs links and tooltips to Memory Base UI (#13265)
* feat(memory): improve memory UI with empty states, toasts, and model filtering

* [autofix.ci] apply automated fixes

* fix: capture memory id/name before setTimeout to prevent stale closure null access

* error improvement and testcase addition

* fix: surface refresh errors, restore API error messages, flatten sidebar ternary, and add keyboard access to batch-size tooltip

* [autofix.ci] apply automated fixes

* biome fix

* fix types

* [autofix.ci] apply automated fixes

* fix: UI polish for Memories and Traces sidebar sections

* [autofix.ci] apply automated fixes

* feat(memories): surface memory config in summary card with popover details

* fix: replace magic index, template literals, and inline noops in Memories/Traces sidebar

* [autofix.ci] apply automated fixes

* ruff fix

* fix testcases

* improve testcase cov import clean up

* [autofix.ci] apply automated fixes

* fix import order

* accessibility fix

* [autofix.ci] apply automated fixes

* feat(memories): make preprocessing instructions required with tooltip hint

* ruff fix

* [autofix.ci] apply automated fixes

* feat(memories): redesign MemoryDetailsHeader UI and update tests

* [autofix.ci] apply automated fixes

* feat(memories): add in-product docs links and tooltips to Memory Base UI

* fix text

* [autofix.ci] apply automated fixes

---------

Co-authored-by: Olayinka Adelakun <olayinkaadelakun@mac.war.can.ibm.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Olayinka Adelakun <olayinkaadelakun@Olayinkas-MacBook-Pro.local>
2026-05-26 14:46:41 +00:00
e192f91eeb fix: ChatBedrock playwright failures 2026-05-25 23:35:48 -04:00
23f3453a65 [autofix.ci] apply automated fixes 2026-05-25 23:35:48 -04:00
7ac198ac37 chore: update opik, perplexity , weaviate 2026-05-25 23:35:48 -04:00
00fe8c001c chore: dup langchain-qdrant to langchain-mongodb 2026-05-25 23:35:48 -04:00
a752275fbd chore: add to deps and loose missing test 2026-05-25 23:35:48 -04:00
a7ae55e5d5 [autofix.ci] apply automated fixes 2026-05-25 23:35:48 -04:00
9224534f70 chore: port over mem0ai changes from 1.10.0
https://github.com/langflow-ai/langflow/pull/13292
2026-05-25 23:35:48 -04:00
cb6d09fc5d chore: update pkg in test_all_modules_importable 2026-05-25 23:35:48 -04:00
1bae798321 [autofix.ci] apply automated fixes 2026-05-25 23:35:48 -04:00
d5bdc6175f chore: update test_all_modules_importable 2026-05-25 23:35:48 -04:00
a11ef3113b [autofix.ci] apply automated fixes 2026-05-25 23:35:48 -04:00