# Changelog — FlowDrop 1.x

Historical changelog for the FlowDrop **1.x** release series (1.6.0 and earlier).
For the current 2.x line, see [CHANGELOG.md](CHANGELOG.md).

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.6.0] - 2026-06-07

> **HTTP API stability.** Endpoints under `/api/*` are `@internal` — they serve the bundled `@flowdrop/flowdrop` JavaScript library and are not a public REST contract. Shape changes between minor releases are expected and are listed below for completeness, not as deprecation notices. See [BC Policy → HTTP API Stability](docs/reference/api/bc-policy.md#http-api-stability).

### Security

- **HITL approvals were replayed across loop iterations** (`flowdrop_stategraph`): In a cyclic StateGraph workflow with a confirmation node in the loop body, the gate only actually paused on the **first** iteration. Loop-iteration clones inherited the original job's `metadata.interrupt_id` — pointing at an interrupt that was already resolved — so the runtime routed the clone to `resume()` instead of `process()`: from iteration 2 onward every confirmation completed instantly with iteration 1's answer, and the user was never asked again. As a second-order effect the trigger-gated consumer downstream of the confirmation could be scheduled twice for one upstream emission, duplicating side effects. **Affected released versions: 1.4.0, 1.4.1, 1.5.0** — sites running human-approval gates inside loops on those versions should update. Fixed by stripping per-execution identity (`interrupt_id`, `execution_time_us`) when cloning a job for a new loop iteration: a clone is a new execution and raises its own interrupt. Pinned end-to-end by `StateGraphInterruptScopingTest` (two-iteration cycle: second interrupt entity, second pause, consumer executes exactly once per resolved interrupt). The 2.x line additionally binds interrupt resolutions to their owning job at the point of consumption — a deliberate breaking change to `ResolvedInterruptInterface` that closes the whole bug class; see the 2.0.0 notes.
- **View-only roles could drive other users' playground sessions** (`flowdrop_playground`): The session send-message, stop, and reset routes were gated by `accessSession()`, which grants on view permissions alone — `view any flowdrop_session` (an observer grant) short-circuits to allowed. So a role holding only that permission could send messages into, stop, and reset **any** user's session, triggering workflow execution (LLM spend, tool side effects) with no `execute` capability. **Affected released versions: every 1.x line that shipped the playground — the send-message route has been view-gated since 1.2.0, stop/reset since they were added.** Fixed by a new `accessDriveSession()` callback on all three routes that requires the `execute session workflow` capability on top of view access, mirroring the canonical session-turn route's execute-vs-view split; admin bypasses are unchanged and the owner-id comparison is hardened with an int cast (matching the session access handlers). Pinned by `PlaygroundSessionAccessTest`.
- **Bulk interrupt dismiss bypassed cancel access** (`flowdrop_interrupt`): The new bulk-dismiss inbox (this release) consumed `dismiss_ids` from the tempstore handover and called `cancelInterrupt()` — which performs no access check of its own — without re-checking access at the point of consumption, making the private tempstore the de-facto authorization boundary. Any producer of `dismiss_ids` (a stale, tampered, or future caller) could dismiss an interrupt the operator could not cancel directly. Fixed by re-checking `$interrupt->access('cancel')` per UUID before cancelling; inaccessible entries are counted as skipped. Applies the same point-of-consumption principle as the HITL fix above. **No published version is affected — the bulk-dismiss feature is new in this release.** Pinned by `InterruptBulkDismissScopeTest`.

### Added

- **Split workflow authoring permissions: `create` / `edit` / `delete flowdrop_workflow`** (`flowdrop_workflow`): Workflow management no longer requires the all-or-nothing `administer flowdrop_workflow` — three new permissions gate creating, editing (including the visual editor) and deleting workflows individually, enforced by a new entity access control handler; `administer flowdrop_workflow` and the global `administer flowdrop` remain full supersets, so existing role configurations are unaffected. All authoring permissions (including `administer flowdrop_workflow`, previously unflagged) now carry `restrict access: true` — a workflow author decides what the workflow executes (LLMs, tools, arbitrary node processors), so splitting the surface does not lower the trust bar and the permissions page says so. Also fixes a gap where the editor's save/create/delete API routes demanded the *global* `administer flowdrop`: a user with only `administer flowdrop_workflow` could open the editor but not save. Roles granted `edit` can load workflows through the editor API without a separate `view` grant; duplicate requires `view` on the source plus create access. The workflow listing accepts any of the authoring permissions (it's the navigation hub for the operations they grant), and the list's Editor/Duplicate operation links now follow entity access instead of rendering unconditionally.
- **Rate Limit node — flood-backed throughput gate** (`flowdrop_interrupt`): New `rate_limit` node (Timing category) caps how often execution passes through — `limit` passes per `window` seconds, with an optional connectable `identifier` (default: a private per-node bucket; share a key to pool the limit, or feed a per-user/per-domain value to partition it). Cross-run coordination comes from core's flood service (the shared counter the Timer machinery lacks); the durable wait reuses the Timer interrupt: a blocked run pauses on a timer paced at `window ÷ limit`, and on resume **re-checks** the bucket — re-arming a fresh timer if other runs took the freed slot, which also means inbox "Resume now" can't bypass the limit, it only re-checks early. Outputs add `throttled` to the timer audit trail. The limit is best-effort (flood check + register are not atomic — racing runs can briefly exceed it; documented); long/non-ASCII identifiers are hashed to fit flood's `varchar_ascii(128)` column. Cron-bound precision: built for "100 calls/hour to this vendor", not per-second throttling (documented).
- **Delay and Schedule nodes — time-based pauses resolved by cron** (`flowdrop_interrupt`): **BC notes for third-party code:** the `@api` `InterruptType` enum gains a `Timer` case — exhaustive `match()` arms over the enum need a new arm (or a default); and the `@api` `FlowDropInterruptInterface` gains `getScheduledAt()`/`setScheduledAt()` — as with core entity interfaces, only the shipped entity is expected to implement it, but external implementers must add the two methods. Feature: two new node processors in a new **Timing** palette category pause the workflow until a time condition is met — `delay` waits a relative duration (value + seconds/minutes/hours/days), `schedule` waits until an absolute datetime (ISO 8601, unix timestamp, or `strtotime()` phrase; the `when` input is connectable so an upstream field can feed it). Both are modeled as a new **Timer** interrupt type: the node throws the standard `InterruptRequiredException` with a new `TimerInterruptMessage` carrying the fire-at time, persisted onto a new `scheduled_at` base field (update 10004) — deliberately *not* `expires`, since `resolveInterrupt()` refuses expired interrupts and a due timer is by definition past its fire time; timers carry no expiry at all, the sweep is their lifecycle. A new `ScheduledInterruptSweeper` (cron, runs before the expiry pass) is the first consumer of the previously unimplemented `ScheduledInterruptHandlerInterface`: it resolves due timers with a NULL uid through the locked resolve path, so the existing `InterruptResolvedSubscriber` chain resumes the pipeline under its declared orchestrator — zero engine changes. Resume precision is cron-bound (documented). The new `TimerInterruptHandler` is also HITL, so a pending timer shows in the inbox as a "Waiting until …" card with a **Resume now** button — skip-the-wait; the node's outputs (`scheduled_at`, `resumed_at`, `drift_seconds`, `resolved_by_timer`, `user_id`) record which path resolved it. A delay/schedule already in the past passes straight through without pausing. New `timer_max_delay` setting caps accepted delays (default 1 year, 0 = unlimited). Requires a pipeline-backed orchestrator; under the stateless sync orchestrator the pause cannot resume (documented).
- **Duplicate workflow** (`flowdrop_workflow`): New "Duplicate" operation on the workflow list opens `/admin/flowdrop/workflows/{id}/duplicate`, a small form that creates a copy of an existing workflow. The full graph (nodes, edges, metadata), exposed ports and schema snapshots are carried over verbatim via `createDuplicate()`; the copy gets its own machine name, a "(copy)" label suggestion, fresh created/changed timestamps and the duplicating user as owner. Admin UI only — no HTTP API endpoint yet.
- **"Available as Tool" checkbox on node types** (`flowdrop_node_type`): New `tool_exposed` boolean on the node type config entity (admin form checkbox, default off). When enabled, the nodes API injects a `tool` output port into the type's metadata — same pattern as the always-injected trigger ports, but opt-in — so instances render a tool source handle in the editor and can be wired as callable tools into any tool-aware consumer or Tool Box, no custom processor needed. The engine needed no changes: tool sources were never type-gated (a tool is just a node reached by a tool edge), so this closes the last gap — the editor handle. Explicit wiring is still required; nothing is auto-attached to consumers, and the invoker allow-list stays graph-derived. **Caveat** (documented in the tool-aware guide): the model's tool arguments are runtime inputs and override instance-configured values for connectable parameters — only expose types whose parameter surface you're comfortable handing to the model.
- **Tool Box node — shared tool sets for multiple consumers** (`flowdrop_node_processor`, `flowdrop_runtime`, `flowdrop`): New `toolbox` node bundles tools into a shared set — wire tools once into its tool input, wire its tool output into any number of tool-consuming nodes (N+M edges instead of N×M); boxes nest. The runtime **flattens** through the box when projecting a consumer's tools, so consumers need no new API (`ToolsAwareInterface` is unchanged), the box itself never executes (tool-only by classification), and the invoker allow-list stays graph-derived — a consumer can still only ever call tools the author wired. What flattens is the new `@api` marker `ToolPassthroughInterface` ("I *carry* tools" vs "I *am* a tool"): a `ToolsAware` node wired in as a tool (agent-as-tool) is deliberately **not** flattened. The traversal lives in a new `ToolGraphFlattener` service shared by the runtime projector and the compiler, so compile-time validation now checks tool-name uniqueness on the **flattened** set each consumer actually receives (a box's label is never a tool name), silently dedupes the same tool node reached via two boxes, and rejects passthrough cycles and nesting deeper than 8 with a `CompilationException`. No frontend changes needed — the node type ships with `visual_type: tool` and renders with the existing tool node UI and dashed amber edges.
- **Budget pauses are resumable** (`flowdrop_interrupt`, `flowdrop_stategraph`, `flowdrop_runtime`, `flowdrop_pipeline`): a run stopped by its execution budget (`timeout` / `max_iterations`) now stamps a machine-readable `paused_reason` (`time_budget` | `max_iterations`; new `PausedReason` constants in `flowdrop_pipeline`) on the pipeline's execution context, and a new `BudgetPauseSignalSubscriber` converts the pause into a pending **system Pause signal** (NULL initiator) — `POST /flowdrop/api/pipelines/{id}/resume` and inbox resolution then resume the run with a fresh budget window through the standard `InterruptResolvedSubscriber` chain. HITL pauses carry no reason and are untouched; an operator's own pending signal is never shadowed by the system one. The reason is cleared when execution re-enters the pipeline and on any non-budget-pause outcome, so it never goes stale. `/api/pipeline/{id}/status` gains **`pausedReason`** so chat frontends can key a "continue" affordance on `status=paused` + `pausedReason` (the existing `pendingInterrupt` is stamped only by the playground HITL path). `PipelineCompletedEvent` (`@internal`) gains a trailing `pipelineId` constructor param and a raw pipeline-context accessor to carry the data. Closes the gap left by the time-budget entry below, which shipped budget pauses as operator-terminal.
- **`StateGraphOrchestrator::executePipeline()`** (`flowdrop_stategraph`): StateGraph now implements `PipelineExecutorInterface`, gaining an execute/resume-existing-pipeline entry point. The effective `ExecutionConfig` (with the resolved checkpoint thread) is stamped onto the pipeline's execution context at creation, so a deferred launch or interrupt resume reconstructs the configuration the run started with; state restores from the thread's latest checkpoint with the entity checkpointer, degrading gracefully to the pipeline's input data with the memory checkpointer. Checkpoint threads resolve in layers: author-pinned `thread_id` → `playground_session_{sessionId}` for session-attached runs → an isolated `pipeline_{id}` default.
- **`PipelineExecutorResolver`** (`flowdrop_runtime`): Pre-built pipelines — deferred launches via `WorkflowLauncher` and both interrupt-resume paths — now execute under the orchestrator their workflow declared, instead of being hardwired to the synchronous job loop. A stategraph-declared pipeline previously lost checkpointing, reducers, conditional-edge routing, and the agent loop (tool projection) on those paths. The sync orchestrator remains the fallback, but the substitution is now logged instead of silent; orchestrator plugins expose an executor-capable service by implementing the new `PipelineExecutorAwareInterface`.
- **`LaunchOptions::$wait`** (`flowdrop_workflow_executor`): Opt-in synchronous launch for PHP callers — `launch()` executes the run inline and returns a `LaunchResult` carrying the final pipeline status instead of `PENDING`, restoring the launch-then-read pattern that deferred execution broke. PHP-only by design (how an HTTP/websocket caller waits is a transport-layer concern, so `/run` does not expose it); ignored with a logged notice for async-declared workflows; execution failures still land on the pipeline status rather than throwing.
- **Inward signal polling and a time budget in the StateGraph loop** (`flowdrop_stategraph`): `executePipelineWithState` now polls `PipelineSignalPollEvent` each iteration — Cancel/Pause signals take effect mid-run and dispatch `ExecutionCancelledEvent`/`ExecutionPausedEvent`, at parity with the sync orchestrator — and enforces the new `ExecutionConfig::$maxExecutionTime`, populated from the orchestrator settings' existing `timeout` field (no second knob); on breach the pipeline pauses like a max-iterations stop. **Behavior change:** the settings form has always defaulted `timeout` to 300, but until now the stategraph loop never enforced it — workflows that previously ran longer than five minutes will now pause at the budget. A budget pause is resumable via the system Pause signal (see the budget-pause entry above); authors of long-running workflows should still raise `timeout` or set it to 0 (no limit) rather than rely on repeated resumes.
- **`WorkflowLauncherInterface` and `POST /api/flowdrop/workflow/{flowdrop_workflow}/run`** (`flowdrop_workflow_executor`): A BC-locked public launch verb so callers can start a run without POSTing a chat message. `WorkflowLauncher` resolves inputs via the workflow schema, creates the pipeline, and defers execution to a post-response processor for sync orchestrators (`DeferredPipelineProcessor`, `needs_destruction`). Ships `LaunchOptions` / `LaunchResult` DTOs and `InvalidLaunchInputException` / `WorkflowLaunchException`. The `/run` endpoint is gated by a new **`execute flowdrop_workflow`** permission so execute can be granted without view or administer. Wave 1 of the session/pipeline/chat refactor.
- **`WorkflowDefinitionInterface`** (`flowdrop`): A storage-neutral `@api` accessor/setter contract (extends `EntityInterface`, `SynchronizableInterface`) extracted from `FlowDropWorkflowInterface`, which now extends it alongside `ConfigEntityInterface`. Additive and BC-safe — every method stays visible on `FlowDropWorkflowInterface` via inheritance and `FlowDropWorkflow` is unchanged. Lays the seam for a future revisionable content-entity workflow backend to implement the same contract, keeping executors / launchers / snapshotter storage-agnostic. Phase 1 of the workflow dual-storage plan.
- **`SessionService::postMessage()`** (`flowdrop_session`): Appends a message with status `COMPLETED` without validating sequence order, flipping the session to `RUNNING`, or launching a pipeline — launch now belongs to the workflow launcher. Splits the room-append concern out of `sendMessage()` ahead of the Concept 2 refactor.
- **Per-session playground URLs** (`flowdrop_playground`): Replaces the single `/playground` page (with `?session=` deep link) with a 1:1 URL-per-session model — `/admin/flowdrop/workflows/{workflow}/session` (list), `/session/add` (create form), and `/session/{session}` (single-session playground, validated against the session's own `workflow_id`). The single-session page locks to exactly the URL's session: the API's `listWorkflowSessions` endpoint gains an optional `?ids=` filter and the controller overrides `endpoints.playground.listSessions` so the front-end only sees the current session.
- **Session-UI gating props on the playground SDC** (`flowdrop_ui_components`): Three new SDC props (defaulted `true`) thread through to the matching `PlaygroundConfig` flags — `show_session_header`, `show_new_session_button`, `show_session_list` — so Drupal callers can drop the header and the create/switch controls to lock the playground to a single session without touching the front-end mount call.
- **Bare full-page editor and playground** (`flowdrop_workflow`, `flowdrop_playground`): New `BarePageRenderer` returns the editor and playground as their own minimal HTML documents that attach only FlowDrop's libraries — never the admin theme — giving the Svelte app the same clean CSS environment as the standalone library demo. Brands the tab with the FlowDrop favicon and neutralizes the UA body margin so the 100vh app fills the viewport.
- **Local tasks and actions surfaced in the editor/playground navbar** (`flowdrop_workflow`): New `NavbarMenuBuilder` service converts a route's accessible local tasks and local actions into navbar items and drives both navbars from it, so contrib modules can add a `*.links.task.yml` / `*.links.action.yml` entry against these routes and have it appear automatically. Adds an **"Edit Settings"** tab (the workflow edit form), a **per-workflow "Pipelines"** tab reusing the global list builder (scoped to the workflow, with the redundant Workflow column/filter hidden; the global action is relabeled "All Pipelines"), a **"FlowDrop Dashboard"** action, and grouped flyout headings ("Tasks" / "Actions") for both navbars.
- **Sessions surfaced in the dashboard and admin menu** (`flowdrop_session`): A "Sessions" admin menu link under the FlowDrop dashboard plus a matching stat card and Manage action-link, each guarded to no-op when the session entity type is absent. `FlowDropSession::getDisplayLabel()` centralizes the `name #<id>` suffix used by the API response and the admin list.
- **Navigable, filterable, bulk-dismissable interrupt inbox** (`flowdrop_interrupt`): Each row links back to its origin (workflow editor, pipeline, and playground session when it still exists) and gains a Node column. Adds a GET filter form (workflow, type, session, message search) applied before the pager, plus per-row and bulk dismiss paths — "Dismiss all" respects the active filter and routes through a tempstore-backed confirm step.
- **Capability marker interfaces** (`flowdrop`): Two empty `@api` marker interfaces let node processors self-declare their nature, detectable via `is_subclass_of` on the plugin class (no instantiation): `BranchingNodeProcessorInterface` (gateway/branch nodes) and `HasSideEffectsInterface` (nodes that mutate state outside the workflow). Mirrors the existing `TriggerNodeProcessorInterface` pattern and is the source of truth for the new capability badges.
- **Microsecond execution timing in the pipeline API** (`flowdrop_pipeline`): Durations in the playground views were derived from second-granularity started/completed timestamps, so any sub-second node showed 0ms. The orchestrators already stamp a precise `execution_time_us` into job metadata (used by the pipeline/job detail pages) — it is now exposed first-class on both `jobs[]` and `node_statuses`, falling back to timestamps for jobs that predate the stamp. The millisecond `execution_time` field stays for backwards compatibility.
- **Site-wide draft persistence policy for the editor** (`flowdrop_ui_components`): Editor drafts contain the full workflow JSON including node config values and, with the library default (`local`), outlive logout — a data-exposure concern on shared devices. New `draft_auto_save` / `draft_storage` settings let site builders disable drafts or pick the storage backend; Drupal now defaults to tab-scoped `sessionStorage`. The UI settings form gains a "Workflow drafts" group, the policy travels to the editor via the `data-ui-config` wire shape (`drafts: { autoSave, storage }`), and `editor.js` sweeps legacy `flowdrop:draft:*` localStorage entries when local storage is not opted into.
- **Node type form overhaul for site builders** (`flowdrop_node_type`): The executor plugin picker gains live client-side search, collapsible provider groups, an "in use" disclosure linking node types already using a plugin, capability badges (Gateway / Trigger / Side effects), and tooltip-expanded descriptions. Selecting a plugin auto-advances to configuration (the no-JS "Continue" fallback is retained). The settings form is regrouped into Basics / Appearance / Advanced with plain-language copy and a parameters value contract documenting runtime precedence (connection > config > default). The node types list gains a Capabilities column. Gateways default to the "gateway" visual type with a live mismatch warning.

### Changed

- **`/api/pipeline/{id}/status` slimmed down** (`flowdrop_workflow_executor`): Now returns just status + timestamps + `pendingInterrupt` for cheap polling; the full payload still lives at `/pipeline/{id}`. The bundled FlowDrop JS library is updated in lock-step.
- **Auto-generated session naming** (`flowdrop_session`): Sessions are named with the workflow label alone (computed before the first save); the list renders them as `<label> #<id>`. Replaces the earlier global "Session N" counter that drifted from the entity ID after deletions, and drops the extra post-save rename. Explicit caller-provided names are still kept.
- **Dashboard ordered by workflow lifecycle** (`flowdrop`): Stat cards, Manage links, and admin menu now follow the run lifecycle — Workflows, Sessions, Pipelines, Jobs, Snapshots, then Triggers and Pending Interrupts — with menu weights realigned to match.
- **Playground session surfaces renamed to "Playground Session"** (`flowdrop_playground`): The editor tab, workflow operations row, list page titles, "New session" action, and flash messages now distinguish the playground *sessions* from the interactive single-session page.
- **`@flowdrop/flowdrop` frontend library** bumped to 1.15.0, picking up grouped navbar flyout support (group label + section headers) and current upstream chunking (variable-service split into `flowdrop-api-variable-service`).
- **Editor connection status indicator hidden** (`flowdrop_ui_components`): The workflow editor mounts with `showStatus: false` so the navbar no longer renders the noisy "Connected" pill in the Drupal-embedded editor where the connection is implicit.

### Deprecated

- **StateGraph approval gates** (`flowdrop_stategraph`): The `approval_gate` node plugin and the entire ApprovalGate surface — `ApprovalGateInterface`, `ApprovalStatus` (both `@api`), `ApprovalGateHandler`, the `flowdrop_stategraph.approval_gate_handler` service, `ApprovalRequiredException`, and the three `ApprovalGate*Event` classes — are deprecated for removal in 2.0.0 in favor of the `flowdrop_interrupt` HITL system (`confirmation` / `form_input` nodes, interrupt entities, `InterruptRequiredException`). The legacy handler stored requests in memory only and had no resolution endpoint, so a pending gate could never actually be approved. As part of the deprecation the `approval_gate` node now pauses through the interrupt system, making it resolvable for the first time via the interrupt REST API and playground UI; its output schema (`approved`, `approval_notes`, `approved_by`, `approved_at`, …) is unchanged. See <https://www.drupal.org/node/3593796>.
- **`SessionService::sendMessage()`** (`flowdrop_session`): Marked `@deprecated`; it will be retired once the playground is repointed to `postMessage()` plus an explicit `/api/flowdrop/workflow/{id}/run` call.

### Removed

- **Legacy `/admin/flowdrop/workflows/{workflow}/playground` route** (`flowdrop_playground`): The 302 redirect kept for back-compat after the per-session URL switch is gone now that all internal links are repointed. External bookmarks to `/playground` will 404; users land on the workflow's sessions list. Drops `playgroundPage` / `playgroundTitle` / `resolveLegacySession` from `PlaygroundController`.
- **Legacy playground tables** (`flowdrop_session`, `flowdrop_session_update_10003`): Drops the orphaned `flowdrop_playground_session` / `flowdrop_playground_message` tables left behind by the `update_10001` migration (guarded, so a no-op on sites that never had them).
- **Obsolete themed-page integration** (`flowdrop_workflow`): Now that the editor and playground render as bare full-page documents, the custom fullscreen html/page templates, their theme hooks, the local-actions `block_access` hooks, and `editor-component.css` (a blanket `:focus` reset that stripped FlowDrop's own focus rings) are removed.

### Fixed

- **Tool Caller declares a real tool input port** (`flowdrop_node_processor`): The `tool_caller` reference processor (`@internal`) didn't follow the consumer pattern its own docs teach — it declared no tool-typed input port, so the editor's dataType-based connection validation offered nothing to drag a tool edge onto, and its string parameter for selecting the tool to invoke was named `tool`, colliding with the reserved port name. The selector is renamed **`tool_name`** and the reserved `tool` input port (data type `tool`) is now declared in its parameter schema, making the node wireable in the editor and a faithful example of both halves of the pattern.
- **Successful nodes no longer report status "unknown"** (`flowdrop_runtime`): `NodeRuntimeService::executeNode()` stamped the success status *before* converting the processor result, and `Output::fromArray()` replaces the DTO's data wholesale — so every successfully executed node reported `getStatus() = 'unknown'` / `isSuccess() = FALSE`. Most visibly, `ScopedToolInvoker` reported every successful tool invocation back to the model as a failure, causing agent loops to retry "failed" tools and duplicate their side effects. The status is now stamped after the conversion. (Field report #5.)
- **Loop clones are gated by their own iteration's branch decision** (`flowdrop_pipeline`): In a cyclic graph (agent loops), `getReadyJobs()` kept one completed job per node, so an iteration-N clone's trigger edge could be satisfied by the iteration-N−1 gateway job while the iteration-N gateway was still in flight — the loop body ran on the False iteration with stale/empty inputs, its completion re-fired the loopback, and the run re-dispatched a finished agent and died at max-iterations (PAUSED, duplicate side effects). A completed source job now gates control flow (trigger edges and `active_branches` data-port gating) only while it is the node's newest job; once a newer job for the same node exists, the old decision is stale and the consumer waits for its own iteration's result. Loop-invariant sources outside the cycle are unaffected (they never have newer jobs). Side effect: a loop-body node now starts one wave *after* its gateway instead of racing it in the same wave. (Field report #6b.)
- **Tool-only nodes no longer execute as ordinary jobs** (`flowdrop_pipeline`): A node whose every outgoing edge supplies a tool (`…-input-tool` target) used to get a pre-generated job with no dependencies — always ready, so it ran once with empty args at the start of every run. Job generation now excludes tool-only nodes (mirroring the compiler's `shouldExcludeNode` rule) and drops tool edges from the dependency graph and readiness checks: tool nodes execute inline via the tool invoker when their consumer calls them, never as jobs. Nodes with mixed outputs (tool + normal) keep their job. The handle-classification rules now live in one place — the new `@api` `Drupal\flowdrop\Utility\EdgePortClassifier` — shared by the compiler's `DependencyEdge` and job generation, whose copies had drifted (`str_contains` vs exact-suffix matching).
- **Untaken gateway branches no longer execute through data ports** (`flowdrop_pipeline`): `active_branches` matching applied only to `-input-trigger` targets, so a gateway branch output wired to a plain data port gated nothing — both branches ran, and on agent loops the act step re-ran with empty `tool_calls` after the final answer, re-fired the loopback, duplicated side effects, and the run died at max-iterations/`timeout` (ending PAUSED, never COMPLETED). A source that declares `active_branches` now gates **all** its named-output edges by branch membership, whatever port they land on; untaken-branch jobs stay idle, the run completes on the taken branch, and the leftovers end SKIPPED. Pinned end-to-end by `StateGraphBranchSemanticsTest` (tool-only exclusion, data-port branch gating, ForEach loop-body regression).
- **`WorkflowLauncher` double-namespaced the orchestrator type** (`flowdrop_workflow_executor`): `resolveOrchestratorAndBundle()` re-prepended the plugin provider to an already-namespaced plugin id, producing `flowdrop_runtime:flowdrop_runtime:asynchronous`. Two consequences, both since Wave 1: async-declared workflows launched via the public launcher never matched the async dispatch check, so they ran in-process after response flush instead of in the durable queue; and the `orchestrator_type` stamped on launched pipelines was unrecognizable to `PipelineExecutorResolver`, so stategraph-declared launches still fell back to the synchronous loop — the stamp shadowed the workflow-metadata fallback that would have routed correctly. Caught by the new end-to-end kernel parity tests (`StateGraphExecutorParityTest`).
- **Workflow config schema lacked `orchestrator_settings`** (`flowdrop_workflow`): The orchestration settings form has written `metadata.orchestrator_settings` since 1.x, but `flowdrop_workflow.metadata` had no schema entry for it, so any strict-schema save (kernel tests, config validation) failed. Added as `ignore` — the per-plugin shape is plugin-defined; tighten to a dynamic per-plugin schema when the shapes settle.
- **`ExecutionConfig::fromArray()` dropped snake_case settings** (`flowdrop_stategraph`): The session execution path and the orchestrator settings form produce `thread_id` / `max_iterations` / `create_checkpoints` / `checkpointer_type`, but `fromArray()` only read the camelCase shape — so chat-session thread continuity and author-declared iteration limits silently fell back to defaults (memory checkpointer, generated thread). Both key shapes are now accepted, camelCase winning ties.
- **Drupal's local actions block hidden on the editor/playground pages** (`flowdrop_workflow`): Now that local actions render in the FlowDrop navbar, the duplicate `local_actions_block` is denied via `hook_block_access`, matched on plugin id so it is hidden before render regardless of theme.
- **Pipelines list defaults to latest-updated first** (`flowdrop_pipeline`): Adds an `id DESC` tiebreaker so pipelines updated within the same second still list newest-first, on both the global and per-workflow lists.
- **Dashboard "Pending Interrupts" counts only HITL interrupts** (`flowdrop`): Counts via the handler manager's `getHitlTypeIds()` — the same human-facing set the inbox queries — instead of `countPendingInterruptsForUser()`, which included operational sentinels and made the card disagree with the inbox it links to.
- **Loop-aware node status reporting** (`flowdrop_pipeline`): StateGraph loops create one job per iteration plus a never-started next-iteration job that is swept to skipped when the loop exits; the `node_statuses` aggregation collapsed all of a node's jobs with last-array-entry-wins, so a node that completed could display as skipped with no timing data. The per-node status is now resolved deliberately: status comes from the latest job by creation order, `last_executed` / `execution_time` from the most recent job that actually started, and additive `executions` / `status_counts` fields expose the full per-iteration picture for the graph's multi-execution (×N) indicators. Kanban and table views report per-job items.
- **Playground deep-links open the requested session** (`flowdrop_playground`): The controller reads `?session=` (numeric ID or UUID) and opens the matching session instead of always auto-selecting the most recent, falling back to most-recent when absent or invalid. Also fixes the interrupt inbox's session links.

### Docs

- **Additive data-flow support for Modeler API** (`docs/architecture`): Draft design doc proposing that Modeler API absorb data-flow workflows (AI agents, n8n / LangFlow style) without disrupting the BPMN/ECA control-flow model — optional typed ports on connections, owner-declared ports, a graph-semantics flag, and type-aware validation, with FlowDrop's xyflow UI offered as the reference data-flow modeler.

## [1.5.0] - 2026-05-27

### Added

- **Pipeline lineage — `parent_pipeline_id` / `root_pipeline_id`** (`flowdrop_pipeline`): `FlowDropPipeline` now records its parentage. `parent_pipeline_id` is `NULL` for a top-level run and points at the spawning pipeline for a nested (shared-session sub-workflow) run; `root_pipeline_id` is the top of the chain and is always populated — a root pipeline self-roots to its own id in `postSave()` (a single-column write on insert, so no save-recursion and no `COALESCE` at read time), and a nested run inherits its parent's root. Installed and backfilled (`root = id` for existing rows, in a batched sandbox update) by `flowdrop_pipeline_update_10003`.
- **`rootPipelineId` / `parentPipelineId` on playground messages** (`flowdrop_session`, `flowdrop_playground`): Every message returned by the playground message endpoints now carries the root and parent pipeline ids of the run that produced it, so messages from a single conversational turn — including those emitted by nested sub-workflows — can be correlated to their top-level run. Resolved via the new `flowdrop_pipeline`-owned `PipelineLineageResolver` (numeric-filtered, existence-checked and session-scoped, so a generic/external `execution_id` cannot false-match), batched to one query per session in `SessionService::formatMessagesForApi()`. Matches the existing `@flowdrop/flowdrop` `PlaygroundMessage` contract. Additive — `executionId` is unchanged.

## [1.4.1] - 2026-05-24

### Fixed

- **`flowdrop_pipeline` installs standalone again** — regression from 1.4.0. The `session_id` base field on `FlowDropPipeline` declared an entity reference to `flowdrop_session`, but `flowdrop_session` depends on `flowdrop_pipeline`, so the pipeline module could not be enabled without the session module also installed (`Field 'session_id' on entity type 'flowdrop_pipeline' references a target entity type 'flowdrop_session' which does not exist`). The field is now declared by `flowdrop_session` via `hook_entity_base_field_info()`, mirroring the existing `snapshot_id` / `flowdrop_runtime` pattern. Storage schema is unchanged.
  - **Provider migration for sites that already ran `flowdrop_pipeline_update_10002()` on 1.4.0** (`flowdrop_session_update_10002`): updates the `session_id` field provider metadata in `entity.last_installed_schema.repository` from `flowdrop_pipeline` to `flowdrop_session`. No table change.
  - **Defensive `hasField()` guards on `FlowDropPipeline::getSessionId()` / `setSessionId()`** so pipeline-only installs (now possible again) and `flowdrop_session` uninstall scenarios do not crash callers.
  - **`flowdrop_session_uninstall()` removes the `session_id` field** from `flowdrop_pipeline` cleanly.
- **PHPUnit test suite restored to green**: 23 test failures in 1.4.0 fixed — pipeline kernel tests (`ModuleInstallationTest`, `JobGenerationServiceTest`, `AsynchronousExecutionTest`) blocked on the install regression above; `PipelineSignalControllerAccessTest` unit tests missing container wiring after the 1.4.0 controller refactor; `WorkflowExecutionServiceTest` unit tests with stale prophecy mocks after the sub-pipeline output extraction refactor.

## [1.4.0] - 2026-05-24

> **HTTP API stability.** Endpoints under `/api/*` are `@internal` — they serve the bundled `@flowdrop/flowdrop` JavaScript library and are not a public REST contract. Shape changes between minor releases are expected and are listed below for completeness, not as deprecation notices. See [BC Policy → HTTP API Stability](docs/reference/api/bc-policy.md#http-api-stability).

### Added

- **Sub-workflow execution via `WorkflowNode`** (`flowdrop_workflow_executor`): Workflows can now invoke other workflows as nodes. Ships a `WorkflowNode` FlowDrop node processor with `flowdrop_workflow` derivatives — one derivative per published workflow — so any saved workflow becomes a callable building block in another workflow. Sub-workflows reuse the parent session (one message thread end-to-end), surface failures and HITL pauses up to the parent, and complete asynchronously via linked interrupts.
- **Typed workflow I/O**: Workflows now declare typed inputs and outputs via schema snapshots and named ports. Adds `WorkflowSchemaResolver` for resolving named inputs at execution time, a workflow schema API, grouped port select, and a typed table in the schema exposure form.
- **`Cancellable` / `Resumable` node processor interfaces** (`flowdrop`): Node processors can now opt in to graceful cancellation (`CancellableNodeProcessorInterface`) and resume after interrupt (`ResumableNodeProcessorInterface`) — used by `WorkflowNode` to drive cancel-cascade and HITL resume.
- **`FlowDropSchemaFormBuilder`** (`flowdrop`): Renders Drupal forms from FlowDropSchema fragments — a documented subset of JSON Schema. Used by interrupt UIs and the typed-I/O workflow forms. See `docs/reference/api/flowdrop-schema.md` for the supported keyword set; out-of-subset keywords (`oneOf`, `$ref`, etc.) fall back to a JSON textarea rather than rendering.
- **Execution lifecycle events and signal polling** (`flowdrop_runtime`): New `JobCompletedEvent` dispatched after each job execution, `PipelineCompletedEvent` dispatched on completion (including exception-based failures) from both the orchestration layer and the synchronous orchestrator, plus a `PipelineSignalPollEvent` for signal polling during orchestration.
- **Pipeline storage registry with bundle-based dispatch** (`flowdrop_runtime`): New `PipelineStorageRegistry` lets orchestrators select an in-memory or persisted pipeline backend per-execution. Adds `BUNDLE_DEFAULT` / `BUNDLE_IN_MEMORY` constants, `PipelineAwareOrchestratorInterface`, orchestrator-aware bundle resolution, and a `RegisterPipelineStoragesPass` compiler pass. The in-memory bundle is installed by `flowdrop_runtime_update_10003`.
- **`FlowDropPipelineRuntimeInterface`** (`flowdrop_pipeline`): Extracted from `FlowDropPipelineInterface` to give the storage layer, orchestrators, events, and queue workers a narrower runtime-only contract. The full `FlowDropPipelineInterface` is now marked `@internal`.
- **Server-rendered HITL inbox** (`flowdrop_interrupt`): New admin UI for pending Human-in-the-Loop interrupts, replacing the JS-rendered prototype. Includes filtering, per-row actions, and a confirm/cancel form. Uses the new `InterruptCreationData` DTO, attribute-based handlers, and the linked / signal interrupt directions added by the interrupt rework.
- **Cron-trigger schedule jitter and overlap policy** (`flowdrop_trigger`): Cron triggers now accept a jitter window to spread load and an overlap policy — `skip`, `buffer`, `cancel`, or `terminate` — to control behavior when a previous run is still in flight.
- **Pipeline re-run with original inputs** (`flowdrop_pipeline`): A new admin action re-runs a completed pipeline with the same inputs that produced the original execution.
- **Advanced filtering on the pipeline admin list** (`flowdrop_pipeline`).
- **Filter forms on all FlowDrop entity listings** (#3590110).
- **UI settings config form** (`flowdrop_ui_components`): Admin-configurable UI defaults (theme, layout) surfaced through a settings form.
- **Pipelines link in the playground navbar** (`flowdrop_playground`).
- **Playground observability — session/playground streaming** (`flowdrop_session`):
  - Per-node job logs stream into the active playground session.
  - Pipeline status transitions are written to the playground as system messages.
  - Session API response now includes execution history.
  - Session messages produced during workflow execution — including assistant outputs, system error messages, and `interrupt_request` messages — now carry the originating `workflow_id` in their metadata, so the playground can attribute output produced by an embedded sub-workflow.
- **Server-driven playground message annotations — `hierarchy`, `tags`, `display`** (`flowdrop_session`): Three new optional fields travel through message metadata and are lifted to top-level wire fields by `SessionService::formatMessageForApi`, matching the `@flowdrop/flowdrop` 1.13.0 contract. Malformed shapes are dropped silently so a bad emitter cannot break the playground response. See `MessageAnnotation` for the closed-enum constants (display modes, tag colors, tag variants).
- **Workflow hierarchy on every playground message** (`flowdrop_session`, `flowdrop_runtime`, `flowdrop_workflow_executor`): Job logs, system notices, and the "started"/"completed" rows inside a sub-workflow now carry an ordered `hierarchy` trail (e.g. `Calculator User ▸ Calculator`). `PlaygroundContext` exposes the resolved trail via `getHierarchy()`; `SessionExecutionService` seeds it from the workflow entity already in scope at the entry point; `WorkflowNode` reads each parent's hierarchy from the same parent-pipeline load that already resolves the session id — zero extra storage access per emitted message.
- **`MessageAnnotation` constants class** (`flowdrop_session`): Closed-enum values for `display` (`bubble`/`log`/`notice`/`card`), tag `color` (six semantic hooks), and tag `variant` (`solid`/`outline`/`subtle`), plus the metadata-key names. Replaces ad-hoc string literals at emit sites.
- **`Duration` utility** (`flowdrop`): Centralized helper replacing inline duration formatting.

### Changed

- **Interrupt creation flow rewritten around a message bus** (`flowdrop_interrupt`): Node processors now throw `InterruptRequiredException` with an `InterruptMessageInterface` DTO (concrete shapes: `HitlInterruptMessage`, `LinkedInterruptMessage`, `InwardSignalMessage`); a catch-side subscriber persists the entity via `InterruptFactoryInterface::create(InterruptCreationData)` and dispatches the bus message. Linked interrupts and signal interrupts are first-class. Message handlers are attribute-based and wired to the factory rather than the manager. **Note for downstream code:** the 1.3.x synchronous creation path is retained as deprecated shims — see the *Deprecated* section.
- **`flowdrop_workflow_executor` adopts the interrupt Message+Bus** for sub-workflow dispatch and the cancel-cascade subscriber, with sentinels scoped to the sub-pipeline id.
- **Pipeline entity id is now the canonical execution id** (`flowdrop_pipeline`, `flowdrop_runtime`): The runtime uses the pipeline entity id as the execution id throughout (orchestrators, events, queue workers, storage, and stategraph), and adds typed overrides for `id()`, `uuid()`, and `save()` on `FlowDropPipeline`. The `InMemoryPipeline` now separates entity UUID from pipeline id and gets a `UuidInterface` injected for deterministic id generation.
- **`PlaygroundController` refactored to constructor injection** with an interface-typed dependency (`flowdrop_playground`).
- **`PlaygroundContext` value object and typed event getters** (`flowdrop_runtime`): Playground-aware events expose context through a typed VO instead of array bags.
- **Per-pipeline state lifted off session metadata** (`flowdrop_session`): Sub-pipeline bookkeeping no longer piggy-backs on the parent session's metadata bag.
- **`SessionService::processMessage` split** (`flowdrop_session`): Execution logic extracted into `SessionExecutionService::executeWorkflowOnSession` for reuse by the workflow-executor path. `SessionExecutionService` carries new constructor args with a deprecation guard for downstream subclasses.
- **Schema exposure form simplified** (`flowdrop_workflow`): Grouped port select and a typed table replace the freeform input list.
- **HTTP API shape — pipeline jobs endpoint** (`flowdrop_pipeline`): `GET /api/pipelines/{id}/statuses` response fields aligned with the OpenAPI document: `started` → `last_executed`, `error_message` → `error`, dropped `job_id` / `completed` / `retry_count`, added `execution_time` (ms). The bundled FlowDrop JS library is updated in lock-step.
- **HTTP API shape — playground messages endpoint** (`flowdrop_playground`): `GET .../messages` now returns `hasMore` as a top-level camelCase boolean (previously a `pagination` object with `has_more`). The `offset` query param has been removed in favor of a sequence-number cursor. The bundled FlowDrop JS library is updated in lock-step.
- **Editor entity operation label** renamed from *"Editor"* to *"FlowDrop Editor"* across entity listings for clarity.
- **`MessageProcessorInterface::executeWorkflowOnSession()` accepts `$parentContext`** (`flowdrop_session`): New trailing optional parameter lets the workflow-executor path pass inherited execution context (including `hierarchy`) into nested invocations. Default `[]` keeps existing callers BC.
- **Pipeline-status messages render as cards only when attention is warranted** (`flowdrop_session`): Failed and paused pipeline completions render as `display: 'card'` with kind (nested only) + status + duration tags. Successful runs at any depth render as a thin `display: 'notice'`, reducing visual noise on the happy path. Nested invocations also suppress the standalone "started" notice — the per-node job logs above carry the activity signal.
- **`@flowdrop/flowdrop` frontend library** bumped through 1.9.0 → 1.10.0 → 1.11.0 → 1.12.0 → 1.13.0 across the release cycle. 1.13.0 adds the server-driven message-annotation contract (`hierarchy`, `tags`, `display`), `awaiting_input` in the `PlaygroundSessionStatus` OpenAPI enum, mobile-responsive playground, container-query log layout, accessibility pass on the message stream, and dependent autocomplete via `autocomplete.params`.

### Deprecated

- **`InterruptManagerInterface::createInterrupt()`** (`flowdrop_interrupt`, [#3591539](https://www.drupal.org/project/flowdrop/issues/3591539)): The 11-parameter synchronous creation method is deprecated and will be removed in 2.0.0. Throw an `InterruptRequiredException` with an `InterruptMessageInterface` from a node processor, or build an `InterruptCreationData` and call `InterruptFactoryInterface::create()`. Calling the deprecated method triggers `E_USER_DEPRECATED`.
- **`InterruptRequiredException::$interrupt`** (`flowdrop_interrupt`, [#3591539](https://www.drupal.org/project/flowdrop/issues/3591539)): The public readonly property is deprecated and will be removed in 2.0.0. Use `InterruptRequiredException::getInterrupt()` for the entity (or NULL for message-shape exceptions) and `getInterruptMessage()` for the message DTO.

### Removed

- **"Open in FlowDrop" entity operation** (#3589742): The standalone *Open in FlowDrop* link was removed from FlowDrop workflow entity listings; the *FlowDrop Editor* operation is the canonical entry point.

### Fixed

- **HITL message-shape end-to-end** (`flowdrop_interrupt`): Unblocks workflows that throw message-shape interrupts and previously stalled at the catch site.
- **Choice interrupt labels preserved** (`flowdrop_interrupt`); stale completion log entries quieted.
- **Sub-workflow failures and HITL pauses propagate to the parent** (`flowdrop_workflow_executor`): A failing or paused sub-workflow now surfaces the correct status on the parent rather than completing silently.
- **`WorkflowNode` resume wired through `ResumableNodeProcessorInterface`** so HITL resume in a sub-workflow continues the parent.
- **Sub-pipeline outputs extracted by pipeline id** (`flowdrop_workflow_executor`): Fixes node-output mapping when multiple sub-pipelines run within one parent.
- **Pipeline pending state refreshed on cascade re-pause** (`flowdrop_interrupt`).
- **Inward signals persisted in a single save** (`flowdrop_interrupt`).
- **Cancel returning null response** (`flowdrop_interrupt`).
- **`PipelineCompletedEvent` dispatch on exception-based failures** from `flowdrop_playground` and the synchronous orchestrator (`flowdrop_runtime`).
- **`INTERRUPTED` status mapped in `getPipelineJobs` summary** (`flowdrop_pipeline`).
- **Execution id propagated through job events and interrupt resume** (`flowdrop_playground`); execution id logged after pipeline creation (`flowdrop_stategraph`).
- **Playground polling**: fixed flood of duplicate-message polling requests, switched to sequence-number cursor (`flowdrop_playground`, `flowdrop_ui_components`).
- **Playground container height** — explicit height assigned so the playground renders correctly inside its host page.
- **Double-slash in absolute API base URL** (`flowdrop_ui_components`).
- **Verbose exception details suppressed** from session UI error messages (no more stack traces leaking to end-user chat).
- **Pipeline entity created upfront** in `SessionExecutionService` so the execution id is assigned before downstream consumers need it.
- **`pipelineStorage` lazy-initialized** in `AbstractOrchestratorBase` to avoid container instantiation order issues.
- **Default `visual_type`** now `"default"` instead of an empty value, removing confusion in new node configurations.
- **Chat processor config**: fixed invalid YAML in the bundled processor config (`flowdrop_chat`).
- **DSL prompt clarified** that JSON array/object values must be on a single line (`flowdrop_chat`).
- **`update-flowdrop` script** no longer exits before bumping the `libraries.yml` version.
- **Missing module dependency** added: `flowdrop_stategraph` depends on `flowdrop_playground`.
- **Unit test failures** fixed in `InMemoryPipeline` and `SynchronousOrchestrator` (#3590114).
- **Static analysis & coding standards**: resolved all PHPStan and PHPCS violations introduced by the pipeline-bundle and workflow-executor branches.
- **Node label no longer duplicated in job log body** (`flowdrop_session`): The playground renders `nodeLabel` from message metadata as a chip in the log row meta column. Repeating it at the start of the body produced rows like `Data to JSON / Data to JSON completed in 2.35ms`. Body is now just the outcome — `completed in 2.35ms` / `failed: <reason>`.
- **Leaf workflow label no longer duplicated on pipeline status rows** (`flowdrop_session`): Removed from both card and notice bodies. The hierarchy chip strip rendered above the body already carries the label.
- **Internal "Pipeline" source pill removed from playground status rows** (`flowdrop_session`): `source` metadata is internal subsystem taxonomy meant to distinguish messages from pipeline/job/cron backends; it leaked into the UI as a small badge with no useful information for end users. Dropped from `PipelineStatusSubscriber` and the pipeline "started" notice in `SessionExecutionService`.

### Security

- **Bundled `@flowdrop/flowdrop` frontend dependency**: Security updates rolled in through the 1.9.0 → 1.12.0 bumps.

## [1.3.0] - 2026-04-28

### Added

- **New `flowdrop_chat` module** (`lifecycle: experimental`): Chat backend for conversational workflow orchestration, supporting two processing modes:
  - **Built-in LLM mode** with `ChatLlmClient` (Ollama, Anthropic), `ChatService` orchestration, and `SystemPromptBuilder` that injects slim workflow state and node type definitions into system prompts.
  - **Workflow-based mode** that delegates chat handling to a FlowDrop workflow, with `GetNodeTypes` and `BuildSystemPrompt` node processors and default chat processor workflows shipped as recipe content.
  - Chat API controller and routing with configurable endpoint config.
  - Admin settings form for provider and mode configuration.
  - `SystemPromptBuilder::nodeTypesJson()` API for retrieving slim node type definitions from a node.
  - Unit tests for `ChatService` and `SystemPromptBuilder`.
  - **Note:** `flowdrop_chat` requires `drupal/flowdrop_ai_provider` (currently `dev-1.0.x`), which is not pulled automatically by `composer require drupal/flowdrop`. Install it separately: `composer require drupal/flowdrop_ai_provider:dev-1.0.x@dev` before enabling this submodule.
  - `hook_uninstall()` deletes all chat history memory records when the module is uninstalled.
- **`FlowDropNodeTypeManagerInterface`**: New public service interface for the node type manager. FQCN aliases are registered for `FlowDropNodeTypeManagerInterface` and all three `flowdrop_chat` services (`ChatServiceInterface`, `SystemPromptBuilderInterface`, `ChatLlmClientInterface`), enabling `$container->get(ServiceInterface::class)` lookups alongside the existing string-ID approach.
- **`TextProcessor` node**: New node type for common text utilities, with documentation in the Text & Output reference.
- **`HtmlToMarkdown` node processor**: Converts HTML input to Markdown, with unit test coverage.
- **`MarkdownExtractor` node processor**: Extracts structured data from Markdown content.
- **`MarkdownToHtml` node processor**: Converts Markdown input to HTML.
- **Tolerant parse mode for `json_to_data`**: New mode that recovers from partial or malformed JSON, improving robustness in LLM output pipelines.
- **`visual_type` and `supported_visual_types`** exposed in node type API data, enabling richer frontend rendering hints.
- **Dashboard-layout library** (#3583166): Replaces `fullscreen-layout` on admin pages with a dedicated dashboard layout, removing the unintended white background.
- **Plugin dependency declarations on `flowdrop_node_type` config entities** (#3583422): Config entities now declare the module dependencies of their executor plugins, so exported configuration installs cleanly on a fresh site.
- **Plugin-driven select for `memory_backend`**: Replaces the freeform textfield with a validated select populated from registered memory backends; prompt templates are validated on save.
- **Workflow description length limit**: Enforces a 1000-character maximum on the workflow description field.

### Changed

- **Drupal-side JavaScript modernized**: Pipeline monitor drops jQuery in favor of `core/once`, and the remaining Drupal-side JS uses arrow functions and tidied JSDoc.
- **Frontend library bumped to `@flowdrop/flowdrop` 1.8.1**.

### Fixed

- Fixed `ChatService::getHistory()` and `clearHistory()` operating on the wrong memory store in workflow processing mode (they now return early when `processing_mode` is `workflow`).
- Fixed `memory_backend` config defaulting to `null` instead of `'entity'` when unset.
- Fixed `flowdrop_ui_components` clobbering the persisted theme preference on initialization, which caused light/dark mode to flicker back to default on page load.
- Fixed `TranslatableMarkup` not cast to string before display in the pipeline form, causing render errors on some labels and descriptions.
- Fixed workflow API controllers swallowing HTTP exceptions instead of re-throwing them, masking 4xx/5xx responses with generic 500s.
- Fixed chat API not returning `workflowId` as `conversationId` in the response payload.
- Fixed toolbar icons not rendering under the Drupal Navigation module (#3583239): adds Navigation-module CSS selectors (`.toolbar-link--*`, `.toolbar-button--icon--*`) and a `hook_page_attachments` that attaches the toolbar library on every page (gated on `administer flowdrop`), since Navigation does not invoke `hook_toolbar()`.
- Removed developer `console` statements from Drupal-side JS components.

### Security

- **Workflow API hardening**: Added ID conflict check on import, complexity limits on submitted workflows, and random IDs to prevent enumeration.
- **Workflow list builder**: Sanitize the `sort` query parameter to prevent injection of unintended sort columns.
- **Chat API rate limiting and observability**: Flood control on chat endpoints, payload size limit, and audit logging of chat requests.
- **Chat prompt-injection hardening**:
  - Sanitize non-JSON `workflowState` payloads before injecting into the system prompt.
  - XML-encode chat history before injection.
  - Remove client-supplied history and enforce a per-message length limit.
- **Reduced error-message disclosure**: Avoid exposing the configured model ID in chat error responses.
- **Chat API `workflowState` size limit**: Array-typed `workflowState` payloads now enforce the same complexity limits as the workflow API (max 500 nodes, 1000 edges), closing a DoS vector where the string-only size check could be bypassed by sending an array.
- **Workflow export documentation**: Warn users that exported workflow configuration may contain sensitive values.

## [1.2.0] - 2026-03-19

### Added

- **Dynamic input/output ports**: Node types can now declare dynamic ports whose number and names are determined at runtime by configuration. Introduces `DynamicPortTrait` for node processors, a `ReservedName` constants class to prevent name collisions, and a new `DataMapper` node processor that maps input fields to output ports dynamically.
- **ExpressionEvaluator plugin system**: New `#[ExpressionEvaluator]` attribute-based plugin manager with four built-in engines — `property_path` (Symfony PropertyAccess), `expression_language` (Symfony ExpressionLanguage), `twig` (Twig), and `jsonpath` (JSONPath via `flowdrop_runtime`). Replaces hardcoded service dependencies in expression-heavy processors with a configurable, swappable architecture.
- **`EventCategory::Integration`**: New enum case for grouping event types triggered by external platform invocations (e.g. Orchestration Invoke).
- **Module installation kernel tests**: `ModuleInstallationTest` now covers all 18 FlowDrop modules via a `@dataProvider`, simulating `drush en <module>` for each and catching missing dependency declarations automatically.

### Changed

- **DataExtractor** (v2.1.0): Replaces direct `JsonPathService` dependency with the `ExpressionEvaluator` plugin system. Adds a configurable `engine` parameter (defaults to `jsonpath` — existing behavior preserved).
- **DataShaper** (v1.1.0): Replaces `DataMapperService` with the `ExpressionEvaluator` plugin system. Adds a configurable `engine` parameter (defaults to `property_path` — existing behavior preserved).
- **DataMapper** (v1.0.0): Switches from hardcoded Twig to a configurable `engine` parameter (defaults to `expression_language`).
- Node type config entities for `data_extractor`, `data_shaper`, and `data_mapper` updated with the new `engine` parameter.
- `OrchestrationInvoke` event type plugin now uses the typed `EventCategory::Integration` constant instead of a bare string.

### Fixed

- Fixed `flowdrop_node_type` not declared as an explicit dependency of `flowdrop_job`, which could cause install failures in a fresh environment.
- Fixed `NULL` node type values causing errors in pipeline job timeline components.
- Fixed `EventCategory` enum not converted to string before use in trigger services.
- Fixed dynamic port `configurable` flag not toggleable by site administrators in the node type form.

## [1.1.2] - 2026-03-18

### Fixed

- Fixed error enabling `flowdrop_playground` caused by a circular module dependency between `flowdrop_pipeline` and `flowdrop_runtime` (#3579822). The `snapshot_id` field on `FlowDropPipeline` is now injected dynamically by `flowdrop_runtime` via `hook_entity_base_field_info()` instead of being declared statically in `flowdrop_pipeline`, eliminating the install-time dependency cycle.

## [1.1.1] - 2026-03-17

### Fixed

- Fixed autowiring failure for `Drupal\flowdrop_memory\Hook\CronHooks` during `drush cr` (#3579749).

## [1.1.0] - 2026-03-15

### Added

- **New `flowdrop_memory` module**: Persistent memory system for workflows, enabling nodes to store, retrieve, and delete stateful data across executions. Includes:
  - `MemoryRead` / `MemoryWrite` / `MemoryDelete` node processors with automatic context-based scope resolution.
  - `MemoryReadRaw` / `MemoryWriteRaw` / `MemoryDeleteRaw` node processors for explicit cross-scope memory operations.
  - `ConversationBuffer` node processor implementing sliding-window message management (LangChain-style memory patterns).
  - Pluggable memory backend system with `EntityMemoryBackend` (database-backed) and `CachedMemoryBackend` (cache-backed) implementations.
  - `MemoryRecord` content entity with database indexes for efficient scope-based lookups.
  - Cron-based cleanup for expired memory records.
  - Configuration form with validated cache bin selection for the cached backend.
  - Value size validation (1 MB limit) to prevent abuse.
  - Comprehensive unit test coverage for backends, entity, and all node processors.

### Changed

- Switched JS library package from `@d34dman/flowdrop` to `@flowdrop/flowdrop`.
- Updated `@flowdrop/flowdrop` JS library to 1.4.0.
- Registered all submodules in `composer.json` PSR-4 autoload.


## [1.0.0] - 2026-03-07

**First stable release of FlowDrop.** All public APIs are now covered by semantic versioning backward compatibility guarantees. See `docs/api/BC-POLICY.md` for details.

### Changed

- Upgraded `@d34dman/flowdrop` JS library to 1.0.0.
- Disabled CI testing against next Drupal major version.

### Fixed

- Fixed build script to restore `build/.gitignore` and `README.md` after Vite build cleans output directory.
- Linter fixes across codebase.

## [1.0.0-beta2] - 2026-03-07

### Changed

- **Trigger module stabilized**: `flowdrop_trigger` promoted from `lifecycle: experimental` to `lifecycle: stable`. `TriggerManagerInterface`, `FlowDropTriggerConfigInterface`, and `FlowDropEventTypeInterface` marked `@api` with full semver BC guarantees.
- **Workflow Executor module stabilized**: `flowdrop_workflow_executor` promoted from `lifecycle: experimental` to `lifecycle: stable`. `WorkflowExecutionServiceInterface`, `WorkflowExecutionMode`, and `WorkflowExecutionStatus` marked `@api`.
- **All experimental modules promoted to stable**: `flowdrop_stategraph`, `flowdrop_interrupt`, `flowdrop_playground`, and `flowdrop_orchestration_connector` promoted from `lifecycle: experimental` to `lifecycle: stable`. Public contracts annotated `@api`; implementation classes annotated `@internal`.
- **BREAKING**: `WorkflowExecutionServiceInterface::execute()` now accepts `WorkflowExecutionMode` enum instead of string constants. `getStatus()` returns `WorkflowExecutionStatus` enum instead of `string`.
- **BREAKING**: `FlowDropEventTypeInterface::getCategory()` returns `EventCategory` enum; `getDefaultExecutionMode()` returns `TriggerExecutionMode` enum.
- `AbstractOrchestratorBase` promoted from `@internal` to `@api`.
- SDC components (`playground`, `editor`, `schema-form`) status updated to stable.
- Updated BC policy to document newly stabilized APIs and removed experimental module list.
- Replaced raw string errors with `ValidationError` objects in stategraph node processors.
- Added missing `strict_types` declarations across codebase.
- Documented `accessCheck(FALSE)` bypass rationale in entity queries.
- Updated `@d34dman/flowdrop` JS library to 0.0.63.

### Security

- Fixed LIKE wildcard injection in workflow search query by escaping user-supplied wildcards.
- Added sort direction and entity field name validation in list builder and node processors to prevent query manipulation.
- Prevented SSRF DNS rebinding by pinning curl to validated IP via `CURLOPT_RESOLVE`.
- Added IPv6 DNS resolution support for SSRF protection.

### Fixed

- Fixed kernel test failures caused by incorrect `DrupalKernel` `app_root` resolution in `ddev-drupal-contrib` environments.
- Fixed permission name mismatches in job and pipeline routing files.
- Fixed missing `nodeId` in `ExecutionContextDTO::toArray()` serialization.
- Added missing return type declarations on public controller methods.
- Added missing `drupal:user` and `drupal:views` module dependencies.
- Added missing `hook_uninstall()` implementations to prevent data leakage on module removal.
- Resolved `EntityInterface` type narrowing issues across controllers and forms.
- Corrected broken BC policy document link in UPGRADE.md.

### Added

- **Configurable access-check user for EntitySave**: The `EntitySave` node processor now supports specifying which user context to use for entity access checks.
- PHP enums for trigger and executor type safety: `EventCategory`, `TriggerExecutionMode`, `WorkflowExecutionMode`, `WorkflowExecutionStatus`.
- SECURITY.md with vulnerability reporting guidelines.
- Unit tests for SSRF protection and IPv6 DNS resolution in HTTP Request node processor.
- API contract tests for 1.0.0 stability guarantee.
- Critical service layer unit tests for 1.0.0 release.
- CI testing against next Drupal major version.
- Site builder documentation with new guides and node reference.
- Script for automatically injecting API docs into MkDocs.

## [1.0.0-beta1] - 2026-02-27

**First beta release. FlowDrop is now feature-complete for 1.0 and entering stabilization. No new breaking changes are planned — only bug fixes and documentation improvements from this point forward.**

### Changed

- **API annotation policy**: Fixed `@api` / `@internal` annotation violations and documented Orchestrator plugin classes in the backward compatibility policy.
- Updated documentation on how to extend components in FlowDrop.
- Updated documentation links.

### Fixed

- Fixed broken test after recent refactor.

## [1.0.0-alpha5] - 2026-02-27

### Added

- **Typed event system**: 26 purpose-built event classes with `readonly` properties replace `GenericEvent` across 5 modules (`flowdrop_interrupt`, `flowdrop_pipeline`, `flowdrop_runtime`, `flowdrop_stategraph`, `flowdrop_trigger`). Each class carries a `NAME` constant for the event name. All events are marked `@experimental`.
- **Orchestrator plugin API stabilized**: `OrchestratorPluginBase`, `OrchestratorPluginInterface`, `OrchestratorInterface`, and `Orchestrator` attribute promoted from `@internal` to `@api` with full semver BC guarantees.
- **Typed constant classes**: `PortDirection` and `EdgeKeys` (`@api`) for workflow edge data; `ErrorCategory` and `ExecutionStatus` (`@internal`) for runtime status tracking. Replaces hardcoded status strings across session, pipeline, and job modules.
- **Exception class hierarchy**: `FlowDropException` base class (`@api`) with typed subclasses `InvalidNodeConfigurationException`, `NodeProcessingException`, and `EntityProcessingException`. All module-level exceptions re-parented under `FlowDropException`.
- **Cron recovery for stuck sessions**: Sessions stuck in `running` or `awaiting_input` for over 10 minutes are automatically reset to `idle` during cron. Processes up to 50 sessions per run.
- **Cron cleanup for interrupts**: Expired, resolved, and cancelled interrupts automatically cleaned up during cron. Configurable via `flowdrop_interrupt.settings` (`cleanup_enabled`, `cleanup_max_age`).
- **Interrupt metadata enrichment**: Flat metadata keys (`allow_cancel`, `confirm_label`, `cancel_label`, `placeholder`, `multiline`, `min_length`, `max_length`) added to interrupt session messages for frontend rendering.
- **Interrupt polling lifecycle**: `onSessionStatusChange` and `startPolling` wired up for real-time interrupt status updates.
- **Job mode trigger messages**: `WorkflowExecutionService` now processes trigger messages synchronously in job mode (#3573704).
- **Concurrent interrupt resolution lock**: Prevents race conditions when multiple requests attempt to resolve the same interrupt simultaneously.
- **LICENSE and CONTRIBUTING.md**: Added project license and contribution guidelines with pre-commit configuration.

### Changed

- **BREAKING**: All FlowDrop events now dispatch typed event classes instead of `GenericEvent`. Event subscribers must update type hints and change property access from `$event->getArgument('key')` to `$event->property`. See UPGRADE.md for migration details.
- **BREAKING**: 32 internal services marked `public: false` across `flowdrop_interrupt`, `flowdrop_orchestration`, `flowdrop_runtime`, `flowdrop_session`, and `flowdrop_stategraph`. Code using `\Drupal::service()` for these must switch to dependency injection.
- **BREAKING**: Constructor signatures changed: `ApprovalGateHandler` (removed `$entityTypeManager`), `StateGraphOrchestrator` (removed `$configFactory`), `FlowDropEndpointConfigService` (constructor removed).
- Replaced `assert()` calls with runtime-safe `if` checks across 15 files. In production (`zend.assertions=-1`), `assert()` is compiled out, silently disabling type guards. Access control handlers now return `AccessResult::neutral()` on type mismatch instead of silently passing.
- Node processors now throw typed exceptions (`InvalidNodeConfigurationException`, `NodeProcessingException`, `EntityProcessingException`) instead of generic `\RuntimeException`.
- Extracted `FlowDropListBuilderTrait` (`@internal`) consolidating shared tablesort, pagination, and status pill rendering logic from entity list builders.
- Replaced `\Drupal::` static service calls with proper dependency injection in entity classes, controllers, and hook classes.
- Interrupt event subscribers registered and pipeline now pauses on interrupt creation.
- Unified interrupt exception hierarchy with correct event subscriber types.
- Removed empty `.module` files no longer needed in Drupal 10+.
- Updated `@d34dman/flowdrop` JS library to 0.0.61.
- Improved UI components accessibility, build configuration, and type safety.

### Security

- **XSS prevention**: Escaped dynamic values in `#markup` render arrays across forms and traits using `Html::escape()`.
- **Input validation**: Validate `EntityQuery` operator and sort direction, `IfElse` operator and `Calculator` operation, `DataOperations` and `DataframeOperations` parameters against allowlists.
- **ReDoS protection**: Harden regex handling with validation, error handling, and pattern length limits.
- **CSRF protection**: Add CSRF token to node validation POST route and provide token to frontend via endpoint config auth headers.
- **Access control**: Add entity-level access checks to Snapshot and Pipeline API controllers.
- **Information disclosure**: Sanitize error messages in API responses to prevent stack trace and internal path leakage.
- **Dependency vulnerability**: Patch rollup dependency for path traversal vulnerability (GHSA-mw96-cpmx-2vgc).
- **API hardening**: Comprehensive SSRF protection, CSRF validation, access checks, and error sanitization across API controllers.

### Fixed

- Restore `flowdrop.module` stub required by PHPStan file discovery.
- Accept session UUIDs in `listSessionInterrupts` route.
- Include `sessionStatus` in playground `getMessages` API response.
- Restore loose comparison semantics for `==` and `!=` operators in `ConditionalEdgeEvaluator`.
- Add error logging and strict comparison in `FlowDropPipeline`.
- Clean up job routing permissions.
- Fix Icon Picker JavaScript library VERSION number mismatch (#3574978).
- Fix misleading node type registration example in DEVELOPMENT.md.
- Make `deferred_message_processor` and other services accessed via `$container->get()` properly public.

## [1.0.0-alpha4] - 2026-02-21

### Changed

- **BREAKING**: Standardized all API response envelopes to `{ success, data, error }` format using new `ApiResponseTrait`. The `message`, `code`, and `count` fields have been removed from responses.
- **BREAKING**: Pagination in `WorkflowsController` moved from HTTP headers (`X-Total-Count`, etc.) to response body `pagination` object.
- **BREAKING**: Hardcoded CORS headers removed from `WorkflowsController`. Configure CORS at the server level.
- **BREAKING**: `StateCheckpoint` entity class renamed to `FlowDropStateCheckpoint` (entity type ID unchanged).
- **BREAKING**: `StateCheckpointInterface` renamed to `FlowDropStateCheckpointInterface`.
- **BREAKING**: `StateCheckpointAccessControlHandler` renamed to `FlowDropStateCheckpointAccessControlHandler`.
- **BREAKING**: Frontend custom events renamed: `schemaform:*` → `flowdrop:schemaform:*`, `fd-plugin-selected` → `flowdrop:plugin-selector:selected`.
- **BREAKING**: Frontend data attributes renamed: `data-fd-*` → `data-flowdrop-*`, `data-schema-form-initialized` → `data-flowdrop-initialized`.
- **BREAKING**: Drupal behavior names standardized: `fdPluginSelector` → `flowdropPluginSelector`, `fdDepNav` → `flowdropDependencyNavigator`.
- **BREAKING**: Removed non-functional `mountSchemaForm.update()` method. Callers should destroy and re-mount instead.
- Replaced hardcoded orchestrator ID checks with capability-based detection using `OrchestratorHelper` methods.
- Extracted large form classes into traits: `TriggerConfigForm`, `WorkflowExecutionForm`, and `FlowDropNodeTypeForm` methods split into focused traits.
- Replaced static `\Drupal::` service calls with proper dependency injection in all entity list builders.
- `FlowDropInterruptInterface` now extends `EntityChangedInterface`.
- `FlowDropInterrupt` entity converted from `@ContentEntityType` annotation to `#[ContentEntityType]` attribute.
- Interface inheritance order standardized to `ContentEntityInterface, EntityChangedInterface, EntityOwnerInterface`.
- Added `: void` return type to `preSave()` in `FlowDropPipeline` and `FlowDropJob`.
- All API controllers made `final` with consistent constructor property promotion.
- Stripped `console.log`/`console.debug`/`console.info` from production JS build.
- Libraries.yml version set to `VERSION` (tracks module version).
- Updated `@d34dman/flowdrop` JS library to 0.0.59.

### Removed

- `FlowDropPipeline::getReadyJobs()` — use `JobGenerationService::getReadyJobs()` instead.
- Non-functional `mountSchemaForm.update()` method.

### Fixed

- Added version query string to inter-chunk JS imports for cache busting.
- Added `detach` hook to `data-viewer.js` to prevent memory leaks.
- Aligned `WorkflowsController` API response with frontend `Workflow` interface.

## [1.0.0-alpha3] - 2026-02-07

### Added

- **`patch_mode` for EntitySave node processor**: When enabled, loads the existing entity by ID and merges only the specified field values, preserving all other fields. Prevents validation failures on required fields during partial updates.
- **Revision support for EntitySave**: `new_revision` and `revision_log_message` parameters allow forcing new revisions on revisionable entities with optional log messages. Outputs `revision_id` after save.

### Changed

- **BREAKING**: `JsonPathServiceInterface` moved from `Drupal\flowdrop_runtime\Service\JsonPath` to `Drupal\flowdrop\Service\JsonPath`. Update `use` statements accordingly.
- **BREAKING**: `PipelineExecutorInterface` moved from `Drupal\flowdrop_orchestration` to `Drupal\flowdrop_runtime\Service\Orchestrator`. Update `use` statements accordingly.
- **BREAKING**: `$storage` parameter is now required in `FlowDropWorkflowSnapshot::createFromWorkflowSnapshot(WorkflowSnapshot $snapshot, EntityStorageInterface $storage)`.
- **BREAKING**: Service ID `flowdrop_runtime.interrupt_persistence` renamed to `flowdrop_interrupt.persistence`. Update any service references or `@inject` annotations.
- **BREAKING**: `WorkflowSnapshot` and `NodeSnapshot` moved from `Drupal\flowdrop_runtime\DTO\Snapshot` to `Drupal\flowdrop_orchestration\DTO\Snapshot`.
- **BREAKING**: `InterruptExceptionInterface` moved from `Drupal\flowdrop_interrupt\Exception` to `Drupal\flowdrop_orchestration\Exception`.
- **BREAKING**: `Message` and `IteratorState` moved from `Drupal\flowdrop_stategraph\DTO` to `Drupal\flowdrop_orchestration\DTO\Snapshot`.
- **BREAKING**: `DrupalInterruptPersistence` moved from `Drupal\flowdrop_runtime\Service\Interrupt` to `Drupal\flowdrop_interrupt\Service`.
- **Converted procedural hooks to PHP 8 attribute-based hook classes** across 7 modules (`flowdrop_workflow`, `flowdrop_job`, `flowdrop_pipeline`, `flowdrop_runtime`, `flowdrop_session`, `flowdrop_interrupt`, `flowdrop_playground`). All hook classes use constructor injection instead of `\Drupal::` static calls.
- **Replaced magic strings with constant classes**: Added `PipelineStatus`, `JobStatus`, `WorkflowSnapshot`, and `NodeSnapshot` status constants. Created 12 `Permissions.php` constant classes replacing 50+ magic permission strings across access handlers and controllers.
- **Split TriggerManager** (1415 lines) into three focused services: `EventFilterService`, `OrchestrationRequestBuilder`, and `TriggerExecutor`. Public `TriggerManagerInterface` remains unchanged.
- **Split SessionService** (1878 lines) by extracting workflow execution logic into `SessionExecutionService` (909 lines). Broke circular dependency with `DeferredMessageProcessor` via `DeferredSchedulerInterface` and `MessageProcessorInterface`.
- **Extracted cross-module interfaces**: `NodeRuntimeServiceInterface`, `WorkflowCompilerInterface`, `StateManagerInterface`, `RealTimeManagerInterface`, `TriggerManagerInterface`. All consumers now type-hint interfaces instead of concrete classes.
- **Decoupled FlowDropSettingsForm** from orchestration module using dynamic service access via `$container->has()`.
- **Centralized per-node initialData injection** in `NodeRuntimeService::executeNode()` as the single canonical location, replacing 4 separate caller implementations.
- Replaced `\Drupal::entityTypeManager()` service locator calls with proper dependency injection in entity classes.
- Improved TypeScript type safety with proper interfaces for workflow editor mount options and lazy-loaded modules.
- Aligned `.info.yml` dependencies with actual code imports across 4 modules.
- Updated `@d34dman/flowdrop` JS library to 0.0.56.

### Deprecated

- `FlowDropPipeline::getReadyJobs()` — use `JobGenerationService::getReadyJobs()` instead. Will be removed in 1.0.0.

### Fixed

- **JS memory leaks and XSS vectors**: Added `Drupal.behaviors.*.detach()` for proper event listener cleanup, replaced `innerHTML` with DOM API construction, fixed race condition in initialization, and fixed DOM dataset deletion across all behavior scripts.
- Per-node initialData injection in `SynchronousOrchestrator` now correctly passes only the subset keyed by the current node's ID instead of the entire initialData array.
- Mixed variable types in `PromptTemplate` Twig context: array/object variables are now spread into context for direct key access while preserving backward compatibility.
- Circular dependency between `flowdrop_runtime` and `flowdrop_interrupt` by relocating `DrupalInterruptPersistence` and using optional service references.
- Integrity constraint violation during session entity migration.
- Replaced `->rowCount()` on raw queries with `SELECT COUNT(*)` for database compatibility.
- Missing `flowdrop_runtime` dependency declaration in `flowdrop_interrupt` module.
- Allow null for `playground_url` SDC prop in editor component.
- Added missing configuration schemas and default config for `flowdrop_runtime` and `flowdrop_playground` settings.

## [1.0.0-alpha2] - 2026-02-07

**Permission system overhaul: introduces granular ownership-based permissions across all modules.**

### Added

- **Granular own/any permissions for content entities**: All content entities now support ownership-based access control with `view own`, `view any`, `edit own`, `edit any`, `delete own`, and `delete any` permission variants.
  - `flowdrop_job`: Replaces flat `view/edit/delete flowdrop_job` permissions.
  - `flowdrop_pipeline`: Replaces flat `view/edit/delete flowdrop_pipeline` permissions.
  - `flowdrop_workflow_snapshot`: Replaces flat `view/edit/delete flowdrop_workflow_snapshot` permissions.
  - `state_checkpoint`: Replaces flat `view/edit/delete state_checkpoint` permissions.
  - `flowdrop_interrupt`: Replaces flat `view/resolve/cancel flowdrop interrupts` with own/any variants.
- **View permissions for config entities**: Non-admin users can now view config entities without full admin access.
  - `view flowdrop_workflow`: View workflows and their details.
  - `view flowdrop_node_type`: View node types and their details.
  - `view flowdrop_node_category`: View node categories.
- **New access control handlers**:
  - `FlowDropStateCheckpointAccessControlHandler` for `state_checkpoint` entities.
  - `FlowDropInterruptAccessControlHandler` for `flowdrop_interrupt` entities with custom `resolve` and `cancel` operations.
- **Entity-level access checks in InterruptApiController**: All interrupt API endpoints now enforce ownership-based access via `$entity->access()` checks.

### Changed

- **BREAKING**: Permission machine names changed for `flowdrop_job`, `flowdrop_pipeline`, `flowdrop_workflow_snapshot`, `state_checkpoint`, and `flowdrop_interrupt`. Flat permissions (e.g., `view flowdrop_job`) replaced with own/any variants (e.g., `view own flowdrop_job`, `view any flowdrop_job`). Reassign permissions after updating.
- **Standardized `administer flowdrop` super-admin bypass**: All access control handlers and custom access callbacks now check `administer flowdrop` as the first bypass, providing consistent super-admin access across all modules.
  - Updated: `FlowDropSessionAccessControlHandler`, `FlowDropSessionMessageAccessControlHandler`, `PlaygroundApiController`.
  - New handlers and rewritten handlers include it by default.
- All routing files updated to include `administer flowdrop` as an OR option alongside module-specific permissions.
- `flowdrop_interrupt` entity now uses a dedicated access control handler instead of the default `EntityAccessControlHandler`.

### Fixed

- Documentation link corrections.

## [1.0.0-alpha1] - 2026-02-07

**This is the first alpha release toward a stable 1.0. Breaking changes from 0.6.x are listed below.**

### Added

- **New `flowdrop_session` module**: Extracted session and message management from `flowdrop_playground` into a stable, standalone module. New entity types: `flowdrop_session`, `flowdrop_session_message`.
- **New `flowdrop_orchestration` module**: Extracted orchestrator contracts (interfaces, DTOs, plugin manager, helper) from `flowdrop_runtime` into a dedicated stable module.
- **New `PipelineExecutorInterface`**: Interface in `flowdrop_orchestration` for pipeline-based execution, replacing direct dependency on concrete `SynchronousPipelineOrchestrator`.
- **Categories API endpoint**: New `GET /api/flowdrop/categories` route serves category metadata (name, label, icon, color, description) for the workflow editor sidebar.
- **API stability annotations**: All classes annotated as `@internal` (no BC promise), `@experimental` (may change in minors), or stable (unmarked). See `docs/api/BC-POLICY.md`.
- **Backward compatibility policy**: Documented in `docs/api/BC-POLICY.md`.
- **Documentation overhaul**: Restructured docs with getting-started guides, user guides, per-module reference pages, and improved MkDocs navigation.

### Changed

- **BREAKING**: Drupal 10 support dropped. Minimum requirement is now `core_version_requirement: ^11.2` across all modules.
- **BREAKING**: `flowdrop_playground_session` entity type renamed to `flowdrop_session`.
- **BREAKING**: `flowdrop_playground_message` entity type renamed to `flowdrop_session_message`.
- **BREAKING**: Session-related service ID changed from `flowdrop_playground.service` to `flowdrop_session.service`.
- **BREAKING**: Orchestrator service IDs changed: `flowdrop_runtime.orchestrator_manager` is now `flowdrop_orchestration.plugin_manager`, `flowdrop_runtime.orchestrator_helper` is now `flowdrop_orchestration.helper`.
- **BREAKING**: `InterruptExceptionInterface` moved from `Drupal\flowdrop_runtime\Exception` to `Drupal\flowdrop_interrupt\Exception`.
- **BREAKING**: `InterruptPersistenceInterface` moved from `Drupal\flowdrop_runtime\Service\Interrupt` to `Drupal\flowdrop_interrupt\Service`.
- **BREAKING**: `InterruptState` DTO moved from `Drupal\flowdrop_runtime\Service\Interrupt` to `Drupal\flowdrop_interrupt\DTO`.
- **BREAKING**: `Orchestrator` attribute moved from `Drupal\flowdrop_runtime\Attribute` to `Drupal\flowdrop_orchestration\Attribute`.
- **BREAKING**: Orchestrator interfaces and DTOs moved from `Drupal\flowdrop_runtime` to `Drupal\flowdrop_orchestration` namespace.
- **BREAKING**: Session permission machine names changed from `flowdrop_playground_session` to `flowdrop_session` (e.g., `view any flowdrop_session`).
- Category color removed from node API responses; now served exclusively by the dedicated `/api/flowdrop/categories` endpoint.
- Editor configuration wired to fetch categories from the new categories endpoint.
- Experimental modules now use Drupal's `lifecycle: experimental` metadata consistently.
- Concrete cross-module class imports replaced with interface-based dependency injection.
- Module dependency graph cleaned up: all circular dependencies resolved.
- Session-aware `InterruptResolvedSubscriber` moved from `flowdrop_session` to `flowdrop_interrupt` (as `SessionInterruptResolvedSubscriber`) to eliminate circular dependency.
- Updated `@d34dman/flowdrop` JS library to 0.0.52.

### Fixed

- Circular dependency between `flowdrop_interrupt` and `flowdrop_playground` resolved via `flowdrop_session` extraction.
- Circular dependency between `flowdrop_runtime`, `flowdrop_interrupt`, and `flowdrop_session` resolved by removing unnecessary `flowdrop_runtime` and `flowdrop_interrupt` dependencies from `flowdrop_session`.
- `flowdrop_interrupt` no longer depends on `flowdrop_runtime` (was circular); now depends on `flowdrop_orchestration`.
- AorB node processor branches now correctly marked as `readOnly`.

## [0.6.0] - 2025-02-05

**BREAKING CHANGE: For users coming from 0.4.x or 0.5.x you would have to resave all your triggers.**

### Added

- **New Node Processors**
  - `Range` processor for generating numeric sequences
  - `Repeat` processor for duplicating values
  - `EntityQuery` processor for querying Drupal entities
  - `EntityContext` plugin for accessing entity data in workflows

- **Workflow Executor Module** (`flowdrop_workflow_executor`) - New submodule for workflow execution

- **Orchestration Improvements**
  - Refactored orchestration layer to allow swappable storage and re-usable code
  - `OrchestratorHelper` service for centralized orchestrator interactions
  - Namespacing support for `OrchestratorPluginManager`

- **UI Enhancements**
  - A better UX for selecting Node Processor Plugin (instead of select list).
  - Permission-based toolbar link visibility (#3571653)

- **Developer Experience**
  - Debug logging for interrupt node processors
  - Unhidden experimental sub-modules for user testing
  - Deriver support in plugin managers

### Changed

- **Design System**: Refactored to use design token-based system for flowdrop UI. You can update the look and feel of FlowDrop UI to match your brand/design (well almost).

### Fixed

- AJAX form callbacks not updating orchestrator and conditions subforms
- Chat outputs now display before checking for nested interrupts
- Session ID propagation to nested interrupt nodes
- Input node detection now uses instance ID instead of type ID
- Text and chat input node detection logic for playground
- Namespaced orchestrator IDs in connector and playground modules
- `PipelineExecutionForm` now uses namespaced orchestrator IDs
- Derivative plugin IDs handling in provider namespacing
- flowdrop JS library version number

### Performance

- Added cacheable responses to `NodesController` API endpoints

## [0.5.1] - 2025-01-27

### Added

- **Playground Enhancements**
  - Configurable auto-run setting for workflows
  - Chat input configuration support
  - Initial session support to auto-select most recent session
  - Display who resolved HIL (Human-in-the-Loop) prompts

### Changed

- **Dashboard Improvements**
  - Consolidated Structure section into Configuration section
  - Simplified main dashboard Configuration section layout

### Fixed

- PlaygroundService::createSession() now properly JSON-encodes metadata array (#3569982)

## [0.5.0] - 2025-01-26

### Added

- **Human-in-the-Loop (HIL)** interrupt system for workflows requiring user input
  - New `flowdrop_interrupt` module with `ConfirmationNode` and `ChoiceNode` processors
  - `interrupted` job status and `awaiting_input` workflow status
  - Playground UI support for handling HIL interrupts
- **Unified Ports** for simplified node data flow
  - Unified input/output port configuration on node types
  - Automatic decomposition of unified inputs with precedence rules
  - Automatic composition of unified outputs from exposed ports
- **Plugin ID Namespacing** - Plugin IDs now automatically prefixed with provider module (e.g., `flowdrop_node_processor:text_input`)
- Gateway mode support for `ConfirmationNode`
- `readOnly` attribute support in JSON Schema for non-editable exposed parameters
- `Entity Save` node processor for persisting entities
- Trigger output port added to all nodes by default

### Changed

- **BREAKING**: Plugin IDs are now namespaced as `{provider}:{id}`. Update hooks migrate existing configs automatically.
- Removed `ChatModel` from core FlowDrop (use `flowdrop_ai_provider` module instead)
- Improved playground UX: user messages display immediately with deferred processing

### Fixed

- Trigger manager WSOD when serializing
- Playground chat getting stuck after execution failure
- Loop iteration results now preserved when HIL interrupt occurs
- Plugin ID sanitization for valid machine name suggestions
- `text_output` renamed to `text` to avoid reserved name conflict
- Composer autoload dependency error (#3569610)

## [0.4.3] - 2025-01-17

### Fixed

- Triggers now properly load and execute orchestrators (stategraph, synchronous, asynchronous, etc.)
- Trigger config and general settings forms now dynamically load orchestrator options (StateGraph was missing from UI)
- Main FlowDrop settings form now dynamically loads orchestrator options

### Changed

- **BREAKING**: Refactored orchestrator system from tagged services to PHP 8 attribute-based plugin discovery
- Orchestrators are now discovered via `#[Orchestrator]` attribute in `Plugin/Orchestrator` directories
- TriggerConfigSchemaBuilder now dynamically builds orchestrator list from plugin manager

### Added

- `Orchestrator` attribute class for plugin discovery
- `OrchestratorPluginInterface` extending base `OrchestratorInterface` for backward compatibility
- `OrchestratorPluginBase` abstract class for easier orchestrator implementation
- Plugin wrappers for all existing orchestrators (synchronous, synchronous_pipeline, asynchronous, stategraph)

## [0.4.2] - 2025-01-17

### Added

- Optional icon picker for node category and node type forms with settings toggle
- Terminal node type added to visual types
- Sortable headers and pagination to all entity list builders
- Execution status column with standardized column order across list builders
- Job dependency navigator component with curved connectors

### Fixed

- Grid component slot rendering now accepts multiple child components
- Crashes when certain modules are not enabled
- Job data section now expanded by default

## [0.4.1] - 2025-01-16

### Fixed

- Form trigger now properly respects enabled/disabled configuration
- Wildcard pattern matching in trigger configuration now works correctly
- TriggerManager serialization fixed using DependencySerializationTrait

### Changed

- Trigger categories (form, entity, user) now default to disabled, requiring explicit opt-in

## [0.4.0] - 2025-01-15

### Added

- Visual Types configuration page accessible from FlowDrop dashboard

### Changed

- **BREAKING**: Removed `execute()` method from NodeProcessorInterface in favor of the clearer `process()` method
- Split EntityTrigger from base Trigger class for cleaner entity-specific trigger handling
- Converted node type form from AJAX-based to URL-based multi-step form for improved reliability
- Removed unnecessary drupalSettings injection from workflow editor

### Fixed

- Workflow editor no longer injects classes into body tag when in fullscreen mode
- Node type form now validates for duplicate machine names
- Node type form Save button now properly submits the form

## [0.3.0] - 2025-01-14

### Added

- New SDC components: editor, schema-form, pill, data-viewer, detail-grid, detail-section, progress-bar, job-list
- Enhanced pipeline detail page with progress bar, job list, and collapsible data sections
- Enhanced job detail page with parent/child node relationships and pipeline link
- Shared FlowDrop library for consistent component behavior

### Changed

- Refactored workflow editor to use SDC component
- Removed flowdrop_ui dependency, consolidated into flowdrop_ui_components

## [0.2.0] - 2025-01-12

### Added

- Event-driven trigger system for automatic workflow execution
- UI components module with reusable workflow editor components

### Changed

- Improved plugin architecture with unified parameter schema
- Enhanced module organization and dependency structure


## [0.1.0] - 2025-01-27

### Added

- Node processor plugin system with PHP 8 attribute-based discovery
- 25+ node processors including AI models, data operations, HTTP requests, and logic gates
- Runtime execution engine with synchronous and asynchronous orchestration modes
- Queue-based execution system for background workflow processing
- Job execution tracking and pipeline management modules
- Node category system for organizing node types

- Initial release of FlowDrop module

