Introduction / Release notes
Verified against Flow 5.0.0-beta.39
Every change in each Boomerang Flow release, newest first, as the product's changelog records it. For the story of what changed and why it matters, see What's new.
Under Upgrading, an item marked Breaking needs an action from you when you upgrade, and links to the upgrade steps.
Note
5.0.0 isn't released yet. These notes are from 5.0.0-beta.39, and can change before the release.
flow.security.bootstrap-token revokes the bootstrap token. Before, a blank variable left the stored
token valid; now the next start deletes it (logged at info), as for a removed declared token. Every instance
must carry the same flow.security.tokens entries, because each revokes the declared tokens its own configuration
does not name.auditor and author are plain users. They were counted as admins for the navigation and
for creating a workspace when users may not, though they never held the global grant; only admin and
operator are admins. Stored users are not changed, and the webapp shows both as User.flow.engine.service.host is gone: set
flow.engine.url (FLOW_ENGINE_URL, for example http://service-core:7700). Its default is
http://localhost:7700, service-core's port; the old default pointed at 7701, where nothing listens.boomerangio/task-flow:3.3.0 (the task settings'
default.image, was 3.1.0), as do the v3 revisions the upgrade moves off worker-flow. Change unit
_0036__DefaultTaskImageTaskFlow33 moves an install whose setting still holds 3.1.0; an image an operator chose is
kept. No seeded revision pins a task-flow version any more: _0043__UnpinTaskFlowImages removes the pin from every
revision naming boomerangio/task-flow, so each follows the setting (the AI, Box and Python worker images stay).
What changes for those tasks:
/usr/local/bin/npm (/usr/bin/npm is gone),
GNU tar, OpenSSL 3.5, curl 8.23 and bash 5.3./workspace, Basic auth is sent encoded, and
the upload verifies TLS, so a self-signed Artifactory fails until its CA is trusted.HTTP_PROXY is set, and JSONPath To Parameter no longer
crashes with module is not defined.flow-service-core and flow-service-dispatcher run on Ubuntu's chiseled
JRE: no shell, package manager or keytool. Set JVM flags in JDK_JAVA_OPTIONS (the JAVA_OPTS variable and
the sh -c java … command the v3 chart used no longer apply), and trust an internal CA by mounting a
truststore and naming it there (-Djavax.net.ssl.trustStore=…) rather than importing a certificate at
container start. The container runs as uid 10001 in group 0.service-core as it starts. The flow-service-loader image and its pre-deploy
Job are gone; remove them from the deployment and give service-core a startup probe that covers the
migration. One product tag builds three images.111). An older v3 install is refused at startup: move it to that chart first.mongo.encrypt.secret/mongo.encrypt.salt now mean the v3 pair, read
once to decrypt v3's secured settings during the upgrade. flow.encrypt.secret/flow.encrypt.salt is the
key v5 encrypts with and is required on every install. Set both before the first start against a v3
database. Secured settings are encrypted with AES-GCM; v3's values are re-encrypted on the first start.workspace (size, mount path, class, access mode) instead of a workspaces list of two types; the volume
keyed by the workflow is gone. A v3 workflow that had Workflow storage is marked on upgrade and refused at run
with WORKFLOW_STORAGE_REMOVED until it is saved again. To keep files between runs, add a Download Artifact
task with Scope: Workflow and Optional at the start of the workflow and an Upload Artifact task with
Scope: Workflow at the end, both with the same name and the path you mounted before; kept artifacts appear
under Configure › Artifacts. Upgraded workflows keep their mount path (/workspace/workflowrun when none was
set); new workflows mount at /workspace. Flow no longer manages the old Workflow storage volumes: copy out
anything you need, then delete them with
kubectl delete pvc -n <namespace> -l boomerang.io/workspace-type=workflow.max.workspace.storage (the run workspace size cap, carried from
max.workflowrun.storage) replaces max.workflow.storage and max.workflowrun.storage in the quotas
settings group; the per-workspace quota field is maxWorkspaceStorage. Kept artifacts count toward
max.artifact.storage until replaced or deleted. The dispatcher's kube.workflow.storage.* properties are gone.POST /api/v2/event used to fail before a run
was saved. A run now gets event, the event's attributes, and data, its data, and no longer one param per
top-level data member: read $(params.data.<field>). The trigger's filters read the event as sent (type,
data.tag). ref still takes a workflow id; add workspace to name the workflow instead
(?workspace=<workspace>&ref=<workflow name>, on POST /api/v2/webhook too), which is the URL the editor now
shows. Without ref, the subject's first segment names the workflow. v3 workflows that filled properties from
a CloudEvent's data with a jsonPath need changing: see "Upgrading from v3" in the README.flow.mode is gone; two properties express the deployment shape. flow.security.enabled (default true)
switches authentication and authorization together, and flow.workspace.single (default false) serves the
seeded system workspace alone. An embedded, headless engine sets false and true. Remove FLOW_MODE from
every deployment. A secured install with no person to sign in takes its first credential from
flow.security.bootstrap-token, registered as a global machine token at start.ai task is removed; the AI tasks are ai-prompt and ai-agent on boomerangio/task-ai:2.0.0.
An upgraded database keeps any ai definition it already holds, so its workflows still resolve, but nothing
maintains it: move nodes to ai-prompt, whose param names are the same. temperature and maxTokens now have
no default and are sent only when set. On service-dispatcher, flow.dispatcher.ai.image is gone: every ai
revision declares its image, which must start with one of flow.dispatcher.ai.image-prefixes (default
boomerangio/task-ai) or the task is refused with ImageNotAllowed. flow.dispatcher.ai.endpoint and
flow.dispatcher.ai.token (blank) reach every ai pod as OPENAI_BASE_URL and OPENAI_API_KEY.flow.uniquenames.enabled is gone. A task run's taskRef on the API, the status events and the dispatcher
protocol is the task's slug form, <slug> for a global task and <workspace>/<slug> for a workspace task, as
workflow nodes already showed; storage keeps the id, so nothing is migrated. A dispatcher's task poll filter
takes the same form: a bare slug now matches only a global task.PATCH; their PUT routes are gone. Send a JSON Merge Patch
(application/merge-patch+json or application/json): PATCH /api/v2/settings with
{"<group>": {"<key>": value}}, for example {"artifacts": {"retention.default.days": "30"}}, and
PATCH /api/v2/parameters/{name} with only the members to change. An absent member is unchanged, null clears
it, and a secured value sent as ***** is kept. An unknown settings group or key is now a 400 SETTINGS_INVALID_KEY rather than ignored.***** and an unset one
reads null on parameters, workflow defaults and node values, task defaults, schedules, settings and runs
(workflow defaults used to read blank; node, schedule and run values used to read ***** even when unset). On a
write, *****, null or a missing value keeps the stored secret, "" clears it, and any other value sets it.
Before, a blank kept the secret: a client that sends "" to mean "unchanged" must send ***** or leave the
value out. On a run request, a param sent as null or *****, or left out, keeps the declared default for every
param type. A value that is only a reference, such as $(workspace.params.token), reads as written.flow.dispatcher.lease-ms), and each heartbeat renews it, so a claim its dispatcher stops reporting is
requeued even when the dispatcher restarts under the same id. Flow's Kubernetes dispatcher and the dispatcher SDK
heartbeat every 30 s from the claim; a dispatcher written against the protocol directly that sends no heartbeat has
every claim requeued 90 s after it was made.slack.signingSecret); with none set, the commands, interactivity and events routes
refuse every request.github.webhookSecret); with none set, every GitHub event is
refused with 401, where before only installation events were. A GitHub event now starts the workflows whose
GitHub trigger is on (it used to read the webhook trigger's switch), its Events and Repository filters apply,
and one workflow refusing no longer stops the rest of the workspace. The run gains an event param,
{type, id}, beside data._0030__SeedSlackCredentialRevisions adds a new revision of each of the 13 Slack tasks, so a token typed into a
node is stored encrypted and read back as *****. Workflows keep the revision they pin until upgraded in the
editor; upgrading keeps the value, which is encrypted on the next save.github-token (refreshed every 45 minutes) and github-url; workspace parameters must be on. Change unit
_0031__SeedGitHubIntegrationParameters gives the GitHub integration template those fields and adds a revision of
each of the 17 GitHub tasks whose token and url default to them; a token typed into a node still wins. A task
upgraded to the new revision and left on its default fails at admission in a workspace that does not share the
token; the editor warns at save. A read-only workspace parameter an integration manages can no longer be changed or
deleted through the workspace (409 PARAMS_MANAGED_BY_INTEGRATION), and readOnly sent on a workspace update is
ignored.slack.clientId and slack.clientSecret are set: Connect installs the Flow Slack app and links that
Slack team to the workspace, and "Share the installation token with tasks" writes a read-only slack-token
workspace parameter. In the Slack app's settings, set the OAuth redirect URL to
<Flow API URL>/api/v2/integration/slack/auth, add the bot scopes channels:manage, channels:read,
chat:write, commands, files:write, groups:read, groups:write, users:read and users:read.email, and
leave token rotation off, since a rotating token cannot be shared. The slack.installURL setting is no longer
read: GET /api/v2/integration/slack/install builds Slack's authorize URL itself. Change unit
_0032__SeedSlackIntegrationParameters makes the Slack template active and adds a revision of each of the 10
Slack tasks that call the Slack API whose token defaults to $(workspace.params.slack-token). The share
route is now POST /api/v2/integration/{github|slack}/share; the GitHub URL is unchanged.google-service-account-key; microsoft-tenant-id, microsoft-client-id, microsoft-client-secret).
Manage edits them, a stored secret kept until it is retyped, and Disconnect removes them. The share route takes
them as values. Change unit _0034__SeedFormIntegrations adds the two templates and marks GitHub and Slack as
integrations Flow installs. No catalogue task defaults to them yet.GET /api/v2/profile returns the user's workspaces as
workspaces (was teams), and the error reasons TEAM_INVALID_REF, TEAM_INVALID_REQ, TEAM_NON_UNIQUE_NAME,
TEAM_INVALID_USER_ROLE and WORKFLOW_TEAM_INACTIVE are now WORKSPACE_INVALID_REF, WORKSPACE_INVALID_REQ,
WORKSPACE_NON_UNIQUE_NAME, WORKSPACE_INVALID_USER_ROLE and WORKFLOW_WORKSPACE_INACTIVE, with the same codes. A
client that matched a reason or read teams moves with them.POST /api/v2/token now refuses an actor that does not fit the token
(TOKEN_INVALID_ACTOR, 400): a global token takes none, SERVICE or AGENT, a user token none.flow.security.tokens.<name>.* registers a global token at start, named by
the key: FLOW_SECURITY_TOKENS_DISPATCHER_HASH, _PERMISSIONS=workspace/read and _ACTORKIND declare dispatcher.
A name is lowercase letters and digits only (an underscore in a variable name becomes a dot), and bootstrap is not
usable. Give a hash (the SHA-256 of the raw token, so the environment never holds it) or a value, optional
permissions such as workspace/read (none for a dispatcher-only token), and optionally actor-kind (default
SERVICE) and expiry. Removing an entry revokes its token, and two entries are valid at once so a token rotates
without a gap. A declared token cannot be deleted through DELETE /api/v2/token/{id}: it answers 409. With none
declared nothing changes.flow.auth.oidc.issuer and
flow.auth.oidc.client-id (FLOW_AUTH_OIDC_ISSUER, FLOW_AUTH_OIDC_CLIENT_ID) fill the auth settings'
oidc.issuer and oidc.clientId at startup, only while they are empty; a value set in Settings is never
overwritten, and a later change to the property does not replace it. The issuer must be an absolute https URL
(http only for localhost), or the start fails. Without them sign-in stays in proxy mode.flow.events.sink.types, statuses (any run status, or terminal) and phases narrow
what is sent, and each destination takes the same keys plus labels (key=value). A transition no sink wants is
never stored, so a filtered event is not counted as dead; labels filter at delivery. phases=completed is the true
end of a run with finally tasks. With no filter keys nothing changes.acquirelock lock it
never released frees it at once, so the next acquirelock on the key succeeds on its first attempt instead of waiting
out the lease; a lock another run holds is never touched.finally runs once the run has ended, any way:
succeeded, failed, cancelled or timed out. The run takes its final status when its graph ends and stays in the
running phase while its finally tasks run in parallel, each with its own timeout under the workflowrun setting
finally.timeout (5 minutes), an operator's ceiling outside the run's own timeout that no workflow can raise; the stage
counts in the run's duration and holds its concurrency slots; the run completes when the last of them ends, and a finally task that fails turns a
succeeded run into failed with the reason FinallyFailed. A finally task reads the run's outcome as
$(context.params.workflowrun-status) and $(context.params.workflowrun-status-reason); a cancel during the finally
tasks does nothing more, a purge still stops them. Finally tasks never chain: saving refuses a finally dependency
on anything but End and any dependency on a finally task (400 WORKFLOW_INVALID_FINALLY). In the editor End gains
an output port; a task linked from it shows a Finally badge and no output port, and its link a fixed finally
condition.externalRef, an embedding product's own id for the record it mirrors:
GET /api/v2/workspace/query?externalRef= answers the one workspace carrying it, within the caller's access, and a
create naming a reference a workspace already carries is 409 WORKSPACE_EXTERNAL_REF_TAKEN with that workspace's
name, so a retried create is idempotent. The reference is set once at create and never patched; every reader of the
workspace sees it, and the webapp shows it on Settings. A unique sparse index, external_ref, joins the inventory.type: workflow (the default), template
or starter, and the last two can be created, versioned and edited only in the system workspace, through its
ordinary workflow routes. A workspace runs a template by reference: a workflow created with templateRef: {name, version} shows the template's tasks and params read-only under its own name, labels and triggers, accepts
run-time values only for the params the template declares, and each submit runs the template's pinned or latest
revision in the workspace, so the run, its quotas and its audit are the workspace's. A template with such
workflows cannot be deleted. A starter is copied, as the Home page has always done. Change unit
_0038__WorkflowTemplatesIntoSystem moves the seeded templates and a v3 install's own into the system workspace as
starters and drops the workflow_templates collection./workflowtemplate
routes. GET /api/v2/workspace/{workspace}/workflow/query lists, beside the workspace's workflows, the templates and
starters it has not made its own, each with its type, and types= narrows; the get, compose, changelog, export and
available-parameters routes answer a template by name when the workspace has no workflow of that name. The first
use makes it the workspace's own: POST …/workflow/{template}/submit creates the link under the template's name
and runs it, and PUT …/workflow naming a template creates the link and saves the display name, description, icon,
labels and triggers. A template's runworkflow task that names another template runs it the same way, as the run's
workspace's link, so a template's children never run in the system workspace on a workspace's behalf. The webapp's
Workflows page shows templates beside the workspace's workflows, tagged and read-only, dashed until first use; the
editor puts the task palette away for one; Home's starters come from the workspace query with types=starter.
The system workspace changes what a workflow is with PATCH /api/v2/workspace/system/workflow/{name} and {type}:
the card's overflow menu offers Make a template, Make a starter or Make a workflow, the system list shows its
starters and tags both kinds, and its templates stay fully editable there; a template other workspaces link stays one.DELETE /api/v2/workspace/{workspace}/workflowrun/{id} removes the run with its
task runs, actions, artifacts and stored logs, and its lineage (the retries that reuse it and the child runs its
tasks started); DELETE …/workflowrun?labels=k%3Dv does the same for every run carrying the labels, a page of 50 a
call, reporting how many remain. A run in flight is cancelled first; one whose tasks are still ending, or held by a
dispatcher awaiting its terminate poll, is removed by a new watcher sweep once they have settled, or after ten
minutes. Both need workflowrun/delete (owner and admin) and are audited as destructive. Audit records stay. A new
sparse index, purge_sweep, joins the inventory.boomerangio/task-flow:3.3.0: GitHub (create an issue, comment on an issue or pull
request, create a pull request, set a commit status, dispatch a workflow, create a release, get file contents),
Microsoft Teams (send a message, create a channel, add a member), SharePoint (upload, download and list files) and
Google Drive (upload, download and share a file). The Google Sheets and Drive tasks gain a revision that takes the
whole service-account key file. Change unit _0035__SeedTaskFlowIntegrationTasks adds them to existing installs.Runs.purge(id) and Runs.purge(labels) call the two run purge routes and read {purged, scheduled, remaining}.flow.events.sink.payload=thin) carries statusReason, as the task event
already did, so a consumer acting on a terminal event reads why the run ended without calling back.attempt, attempts (how the superseded attempts
ended, with their dispatcher) and, on a terminate order, statusReason; a workflow run order has statusReason;
and the end request takes retryable: false to mark a failure final, which the engine never requeues whatever
the node's retry setting. The SDK's TaskFailure.terminal sends it. A task order also carries retry: {max, failures}, the node's budget and how much of it the engine has spent, so a handler that acts differently on a
failure Flow will not retry needs no count of its own; the SDK's TaskRun.isLastAttempt() reads it. Additive,
protocol version 1.retry: {max, backoff: {type, initial, max}} on the
node, edited on the task's Configure tab. The engine requeues the task on that budget with that backoff, never
for a cause a retry cannot change (ImageNotAllowed, AdmissionDenied, ResultsTooLarge or an engine-written
cause), and records every superseded attempt on the task run as attempts, which the run's task view lists. A
task without the setting runs once, as before; the engine's own requeues of an attempt that never ran keep their
separate budget.concurrency: {limit, overflow} on the workflow, edited under
Configure → Execution. A run over the limit is parked as waiting until a run of the workflow ends (queue, the
default) or ends at once as cancelled with reason ConcurrencyLimit (skip). The workspace quota keeps
precedence. A workflow run now carries statusReason beside statusMessage, as a task run does.flow.dispatcher.poll.recheck-ms sets how often a held dispatcher poll looks at the claimable page again
(default 1000, floor 100). A lower value shortens every hop of a sequential chain at the cost of more indexed
queries per dispatcher: its idle queries per second are its pools × 1000 / the value.captureTaskLogs(new TaskLogCapture()) on the builder, the lines a handler logs (tagged taskRunId in the MDC,
the first and last 64 KB kept) are uploaded as the task log when it ends and every 30 s while it runs; with
observationRegistry(registry), each handler runs under a flow.task observation carrying the task's
traceparent. Both are off by default; a logback appender is included, SLF4J and logback stay optional.GET /api/v2/workspace/{workspace}/insights/tasks counts the period's task runs per task,
type, workflow or label, optionally per hour, day or month, with mean, p50, p95 and max duration, the tokens and
cost of its AI task runs, the wall clock of the runs sharing a label, and the slowest task runs. The Insights page
shows it under Task runs, grouped by task unless another grouping, Model and For-each item included, is chosen.GET /api/v2/workspace/{workspace}/taskrun/query pages them by label or
run (one of the two is required), workflow, task name, status and date, as the run query pages runs. A row
leaves out params, results, spec, annotations and status message; read the run for those.attempt once a dispatcher has claimed it: 1, plus one per requeue. The run view shows it when a
task was requeued.boomerang.io/model, the model its model param asked for, and every
item task run of a for-each, and a child run a for-each run-workflow task started (on the run and its task runs),
carries boomerang.io/item, its item.
Labels under boomerang.io/ are now reserved for the server, as annotations already were: a client's are
dropped on submit, start and retry.POST/GET /api/v2/callback) takes an optional eventId. A repeat of the same
callback for the same run is acknowledged and not applied again for 7 days; without one, each delivery is
applied as before.PUT …/workflowrun/{id}/retry takes
an optional {scope: all | failed | nodes, nodes, params, labels}. The rest is carried over as copies marked
reusedFromRef, and the run records retryFrom. The run page's Retry run and each task's Retry from here open
the same choice. The new run starts with an empty run workspace, so files the carried-over tasks wrote there are
not carried over; pass them as workflow-scoped artifacts, or re-run everything. The dialog says so.secret is a parameter type. Secret values are encrypted at rest under flow.encrypt.*, masked on every read,
and never written to a log. The Box tasks' privateKey is one: _0044__BoxPrivateKeyIsASecret types it as a
password and seals the value a workflow already holds for it. The placeholder default a few older GitHub revisions
and the SendGrid template task carried on a credential parameter is cleared (_0045__ClearPlaceholderDefaults).detail, never its value.WORKFLOW_INVALID_CHILD_PARAM (1218). A child run receives only the params its node passes, plus item and
index under for-each; the parent run's params are not handed down, as in v3.ai-prompt and ai-agent, on boomerangio/task-ai:2.1.0, takes images: images lists
run-workspace paths or https URLs, with imageDetail and maxImageBytes (5 MiB). The image also exports
OpenTelemetry spans when an OTLP endpoint is configured. Revision 1 stays on 2.0.0 for workflows pinned to it.TRACEPARENT derived from its run and task run ids, so one run's tasks share one trace, and
TaskRun.traceparent() in the dispatcher SDK computes the same value. On service-dispatcher,
flow.dispatcher.ai.otel.endpoint, .headers and .protocol (blank by default) give ai pods the OTLP
exporter settings. Flow stores no traces; point them at Langfuse, Phoenix, LangSmith or Tempo.labels and roll up AI usage: tokens, cost and a breakdown by model for the period, and
tokens and cost per task.RateLimited, which requeues it with backoff like ExceededQuota.ClusterUnavailable instead of DispatchError, and the engine requeues it with backoff like ExceededQuota;
other 4xx answers keep their reasons. Before the next attempt runs the task's Job or TaskRun is removed, so it never
runs twice at once.DispatcherStopping, which the engine
requeues at once instead of after the 90 s lease. Dispatcher.stop() in dispatcher-sdk now waits for the poll in
progress within its 10 s drain timeout rather than abandoning it; a host that embeds the SDK can set drainTimeout
to 30 s or more to cover a poll the engine holds for its full window.$(workspace.params.x) replaces $(team.params.x), which still resolves, logs a warning, and is removed in the
next major version.dispatcher-sdk adds StatusReason, naming every reason a dispatcher sends; TaskFailure's four reason
constants are the same values.contracts/api-v2.yaml, generated from the document
service-core serves at /api/docs/spec/api-v2 and held equal to it by a test; an integrator reads the routes
and models from the file at the tag they run.flow-sdk, a Java library for the public API: Flow.connect(url, token) then typed calls to submit, start,
read, pause, resume, cancel and retry workflow runs, query runs and task runs by label, read a task run's
stored log, read, query and decide manual and approval actions, read Insights and probe the server's health,
with the server's error carried as FlowApiException. It depends on spring-web and Jackson only, and is
published to GitHub Packages by every product tag beside dispatcher-sdk, at the tag's version
(flow-sdk/README.md).foreach: true in the public TaskRun, and the task-run query takes
foreach=true|false to list parents only or leave them out; absent lists both as before. A parent stands for its
items in the graph and never runs, so a running or waiting count over a run's task runs can now exclude it without
reading the workflow. flow-sdk adds TaskRunQuery.foreach(boolean) and TaskRun.foreach().features setting, Workspace creation by users (workspaceCreate, on by default), says whether a user who is not
an admin may create a workspace; Home and Workspaces offer the button only to those who may.GET /api/v2/insights and GET /api/v2/insights/tasks compute the
workspace summary and task breakdown over the workspaces named by workspaces, or every workspace, and
GET /api/v2/insights/taskruns lists task runs newest first with their workspace, by label. A workspace-scoped
grant is refused. The SDK gains Flow.insights() with InsightsQuery.workspaces(...) and taskRuns(...).features setting Auto-Create Users (autoCreateUsers, on by default, which is how
sign-in has always worked), switched off, refuses a person who is not a user yet, unless an admin has added them to a
workspace or the admin claim below makes them an admin. A person added to a workspace by email is activated by their
first sign-in (it was refused before). _0046__SeedAutoCreateUsersSetting adds the setting to an upgraded install.flow.security.admin-claim.name and flow.security.admin-claim.values
(FLOW_SECURITY_ADMIN_CLAIM_NAME, FLOW_SECURITY_ADMIN_CLAIM_VALUES, comma-separated) name a claim, a string or an
array such as roles or groups, and the values that make its holder an admin. With a claim set, the provider
decides admin on every sign-in that carries the token: a holder is made an admin, one who no longer matches is made
a user and their sessions end, and the first sign-in on an empty install is not an admin by being first. Unset,
nothing changes. The signature-checked id_token of the webapp's sign-in is always read; the unverified JWT an
OAuth2 Proxy forwards is read only with flow.security.admin-claim.trust-forwarded-token=true (default false).PUT /api/v2/activate checks the code before it registers the
caller). With an admin claim set, a sign-in whose claim is read (the webapp's, or a trusted forwarded token) is decided by the claim and the code is not used; one that cannot read it (a forwarded email, an untrusted forwarded token) still needs the code on an empty install. The bootstrap token and other machine tokens are not
users: they work from first start and never activate the install. Once the install has users the activation
endpoint refuses every code.FLOW_ACTIVATION_CODE (flow.activation-code), and it has no default. The old
FLOW_OTC / flow.otc is still read when the new name is unset and logs a deprecation warning; both set to
different values stops the start. The code must be at least 32 characters or the start fails, and with none set
nobody can create the first admin and the start warns: set it before a fresh install is first signed in to. The
built-in default test is gone. The compose stack and the local Spring profile set a fixed development value;
the e2e setup types it. The otc field of PUT /api/v2/activate is unchanged.$(workspace.params.google-service-account-key), the Google integration's parameter, as the Drive file tasks
do. A workflow moves when it is upgraded in the editor. Upgraded, a node that still signs in with Client Email
and Private Key in a workspace without the Google integration fails at admission with no such workspace
parameter, and the editor warns at save; type the key into Service Account Key, or connect Google. Where the
workspace has the parameter, its key is used ahead of Client Email and Private Key. Change unit
_0037__SeedGoogleKeyDefaultRevisions adds the revisions to existing installs.boomerangio/task-flow:3.3.0 (was 3.2.1), whose
runtime base moved from an end-of-life Alpine 3.20 to 3.24; the artifact commands are unchanged. An operator who
pins flow.dispatcher.artifact.image keeps their own value.flow.watcher.provision-retry-ms, 60000), failing on the third attempt. A dispatcher built on an older SDK keeps
the sweep's release.initiatedByRef, and reads name them in the new
initiatedByName; the run page and Activity's Initiated By column show the name.workspaceMemberCount).task settings (timeout
ceiling, default image, deletion policy) when the run is queued, so a retry, a run migrated from v3 included, takes
them as they stand then. A run's only boomerang.io/* annotations are generation and kind; a submit or start
that sends one has it dropped.service-core and service-dispatcher. Compact object headers take 4 bytes off every
object on the heap. Both are set in the image's command, under any JDK_JAVA_OPTIONS a deployment adds; an
option there that changes the heap layout (another garbage collector, headers off) makes the JVM skip the cache
with a warning and start as before. The images ship the application extracted (service.jar plus lib/),
which the cache requires, and each is about 120 MB larger for the cache; the health probe and ports are
unchanged. The dispatcher handles requests on virtual threads, as service-core already did.nimbus-jose-jwt 10.3, the Slack client
1.45.3 (for its okio 3.4), body-parser 1.20.9, axios 1.20.0 and dompurify 3.4.17.GET /api/v2/context no longer carries platform.privateTeams, which was always false.futureIn,
futurePeriod, timezone and time, matching the run-workflow task.wfe-url context param points to the wait-for-event route, /api/v2/callback.currentRunTotalDuration, currentRunAverageDuration) cover the
whole UTC calendar month. They used to stop at midnight before the month's last day, so that day's runs were
never counted.retriedRuns counts the runs that are a retry of an earlier run, by hand or automatic. It
counted the runs whose workflow allowed retries, whether or not any happened.approvalStatus (approved or rejected),
approvalDate (ISO 8601, UTC), approvalUserName, approvalUserEmail and approvalComments, taken from the
approver whose action decided it, so $(tasks.<name>.results.approvalStatus) reads a value instead of an empty
string. Its "Number of Approvals" setting (numberOfApprovals) is honoured; the engine read a different name,
so every approval completed with one approver. numberOfApprovers is still read where a definition sets it.
Each approval is kept as it is given, so one that needs several approvers completes when the last of them acts.id is the outbox row's id, the same
on every delivery of that transition, and time is when the transition occurred. The graph's end node sends no task
event; the run's own event reports the end, and a skipped or refused task and a workflow timeout send one event
each./api/v2 does. An audit record of a run that a dispatcher's end call completes names that token as the
actor, where it named system./internal/v1/auth/{config,exchange,logout}, outside the public
/api/v2. Route /api/v2/** from outside and leave /internal/v1/** unrouted./dispatcher/v1. service-core and service-dispatcher move together under one tag. Route
/dispatcher/v1/** to service-core only for a dispatcher that runs outside its network.service-core when it has an artifact store. The dispatcher uploads a finished task's
log (PUT /dispatcher/v1/taskrun/{id}/log, capped by flow.engine.task.log.max-bytes, 10 MiB) and core serves that
copy once the task has ended, so a log outlives the pod and needs no dispatcher up. Otherwise core reads the live
log from the dispatcher that holds the claim, at the URL it registered: set flow.dispatcher.logs.url on
service-dispatcher (for example http://service-dispatcher:7702/api/v1/logs/stream). Core's
flow.agent.service.host and flow.agent.logstream.url are gone: nothing on core names a dispatcher, and a
dispatcher that registers no URL has no live log to follow. The dispatcher's live log route, /api/v1/logs/stream,
accepts only the bearer it generates at start and registers; nothing but core can read it. Its v3 GET /api/v1/logs
read is gone, and it serves no OpenAPI page. Two new keys in the Artifacts settings group govern storage: "Store
Task Logs" (on) and "Task Log Retention (days)" (7), apart from artifact retention; stored logs never count toward
artifact storage. Set flow.dispatcher.logs.upload=false on a dispatcher to keep its logs in the pod only.labels entry without a value, such as labels=team, is a 400 QUERY_INVALID_FILTERS everywhere instead of a server error on most lists. A label is split once on its first =,
so labels=a%3Db%3Dc matches the value b=c (it was read as b). The user list's ids filter now matches. The
action list's and action counts' toDate is exclusive, like every other query. statuses and phase are read
ignoring case on the run, task, workflow, user, workspace and schedule lists.service-core and the Kubernetes dispatcher, instead of
arriving whole when the task ends. Read before its container starts, it shows one waiting line and then the live
log.password default reads *****
and is kept when the YAML is applied back unchanged.failed with reason
DispatchPrepareFailed, and the rest of the poll is still handed out.tokens.<name> context parameters are gone. They held a token's stored hash, which cannot authenticate, under
a name no reference could reach. A task that needs a credential takes it from a secret parameter.500.
The configured token now takes the value over and the minted one is deleted (logged at warn); two configured tokens
sharing a value stop the start, and a value two stored tokens hold is refused (401), never a 500.403 AUTH_USER_NOT_ADMITTED, not a 500, when Auto-Create Users is off and the
person is not a user yet. The sign-in page now shows that refusal's own message ("Ask an admin to add you to a
workspace") instead of blaming the identity provider.PUT /api/v2/activate behind a proxy answers a wrong, missing or oversized activation code with
400 AUTH_ACTIVATION_CODE_INVALID and its message, as the OpenID Connect sign-in already did, instead of a bare
400. An error response whose status is not 401 no longer carries WWW-Authenticate.permissions are optional. With none, the token holds no grant and can call only routes that
need no permission, which is the dispatcher protocol; any permission given is still validated. A dispatcher-only
token no longer has to name a grant it never uses.audit retentionDays) through PATCH /api/v2/settings now reaches the
audit expiry index on every instance within a minute (flow.audit.retention-interval-ms), instead of at the next
restart. The 60-day floor is unchanged. The run (workflowrun retention.days) and task log retention settings
already took effect without a restart.500, when the dispatcher holding
the task cannot be reached (and no stored log counts) or its stream breaks part way.service-core and service-dispatcher no longer crash at start with SIGILL on some machines. The JVM cached
call adapters in the image's AOT cache, and an adapter recorded on the build machine could fail to link on another
CPU; both images now record and start with adapter caching off. Class loading and linking stay cached.dispatcher.logging.type=loki) select the labels the dispatcher sets on the task's pod, as a
log shipper's labelmap names them (boomerang_io_taskrun_ref, boomerang_io_workflow_ref), and the task container
of either executor (container=~"task|step-task"). The query asked for v3's bmrg_* labels and Tekton's container
only, so it matched no v5 task. The log shipper must map pod labels and the container name under those names.GET /api/v2/integration/slack/install) answers 409 INTEGRATION_NOT_CONFIGURED while the
Slack app's client ID or secret is unset, instead of 404 INTEGRATION_INVALID_REF.contracts/api-v2.yaml) declares authentication: a bearer token or x-access-token on every
route, none on the GitHub callback and Slack routes, and the access_token query parameter beside them on /webhook,
/event and /callback. It no longer declares the System tag twice, reuses an operation summary, omits a path
parameter or lists Spring's HttpHeaders as a request header.retry.max above 0) of a type the engine never requeues, with
WORKFLOW_INVALID_TASK_RETRY (1223) naming the task. Such a retry, on a runworkflow or sleep node for example,
was stored and never applied; a run-workflow task's retries belong on the tasks of the workflow it runs. The editor
already offered retry only on the requeueable types; a definition saved through the API with such a retry must drop it.retry, with its own budget: a failed item is requeued with the node's backoff
while its siblings' budgets are untouched, and the dispatcher's order states the budget, so an item's first attempt
is no longer reported as its last. Items used to carry no retry and ended on their first failure.runworkflow task all run as the same link. A linksTemplate relationship edge claims the workspace's link to the
template. Workspaces that already hold duplicate links keep them; reads take the first, and each delete by name
removes one.releaselock task frees the key an acquirelock in the same run took. It used to match only the acquiring task
itself, so it succeeded without releasing anything, and the key stayed held until the run ended or the lease expired.
A lock another run holds is still never touched.timeout in milliseconds, the unit the engine has always read it in, and defaults
it to 300000 (5 minutes): revision 1 said seconds and defaulted to 300, a 300 ms lease. Change unit
_0040__SeedAcquireLockMillisecondsRevision adds revision 2 to an install holding the task; a workflow pinned to
revision 1, or one that stored the old default, keeps its value until it is edited.$(params.item) and $(params.index) again: the item and index the engine
sets at fan-out are context params, so the rule that a task param never reads the task's own params
(2026-10-29) no longer fails every item with ParamUnresolved._0033__SeedSendGridTemplateRevision adds it to existing installs; a workflow moves when it is upgraded in the
editor.POST /api/v2/integration/github/unlink takes the
integration's record id or the installation id, refuses a caller who cannot reach the workspace (integration write
permission in any workspace could unlink another workspace's GitHub by its installation id), answers 404 for an
unknown installation, and is audited.POST /api/v2/integration/slack/{commands,interactivity,events} and
GET /api/v2/integration/slack/auth no longer require a Flow credential; each checks the request itself (the
Slack signature, or the OAuth code exchange). GET /api/v2/integration/slack/install still requires sign-in, and
the unused /api/v2/extensions/slack/install permit entry is gone.