ws:// and wss:// URLs dial out and hold a supervised connection; a path is
served under /hooks/<flow>/ with an optional secret. Outputs only listen,
inputs send. Frames are JSON keyed by port, with json_key and on_connect.
fluksio.flow.connector_tls: trust the certificate a device presented on
first contact, kept in ConnectorNode.store, and name the new fingerprint
when another one is refused. Additive, so no contract version bump; hue
and wfrac use it.
The generated batch script exported nothing and passed `--token`, which
any user on a shared compute node reads from `ps`. It now exports
FLUKSIO_WORKER_TOKEN after `prerun`, which the worker already reads.
`ConnectorNode.store` keeps short strings per connector package in the
engine's database (`connector_state`, migration f8c3e5a91d27), so the nodes of
one package share them and `fluksio serve` needs nothing new: get, set and
delete, at most 64 kB a value and 256 keys a package. A superuser lists the
keys (never the values) and clears them through `/connectors/{node_type}/state`,
or under Stored state in a connector node's panel. Documented in the connector
contract.
The enrolment exemption covered any *.local host, which mDNS answers with
whoever claims it; it is now localhost, 127.0.0.0/8 and ::1. /docs, /redoc
and openapi.json stay closed in production and otherwise answer only a
loopback caller unless API_DOCS_ENABLED is set, which the dev compose does,
so `fluksio serve --host 0.0.0.0` no longer hands the schema to the network.
fluksio-worker now refuses ws:// and http:// engine and artifact URLs unless
they are loopback or --insecure is given; the engine still accepts both.
An artifact reference must be sha256: plus 64 hex digits before it is joined
into a path, and node code gets the host environment through the engine's
credential denylist instead of all of it.
The limits read the leftmost X-Forwarded-For entry, which the client writes,
and the LAN nginx appended to it. The address now comes from uvicorn's
proxy-headers middleware (rightmost hop not in FORWARDED_ALLOW_IPS), applied
in the app too so tunnel-replayed calls resolve the same way; nginx sets the
header to $remote_addr and the compose backend trusts the Docker networks.
/users/signup gets a per-address limit of 10 an hour.
DELETE /flows/%2e%2e reached FlowStore.delete_flow(".."), which removed
the data directory, and /flows/_lib removed every shared node. Every
path the flow and dashboard stores build from a flow, node, library or
dashboard name now goes through checked(), which raises InvalidName
(400) for anything _validate_name refuses. _validate_name uses
fullmatch, so a trailing newline is refused too.
Writing node source, installing modules and any flow save that adds,
retypes or repoints a code node (Function, Command), edits a Command
node's settings or changes a node's resources.env now answer 403 for
everyone but a superuser. MCP tools and the assistant reach the same
routes, so they inherit the gate. Node types report runs_code.
The editor shows code read-only with a note, disables a Command node's
settings, leaves code nodes and reusable nodes out of the palette and
paste, and the Modules page offers no install.
/users/signup answers 403 unless SIGNUP_ENABLED is set. A public
GET /utils/config/ tells the login page, which hides the sign-up link,
and the signup page explains that an administrator creates accounts.
The docstring still justified the ANSI colours by the old one-hue web ramp.
The web now draws from the categorical --chart-1…8; the terminal keeps named
ANSI colours so it follows the user's terminal theme.
/search lists each input and batch output under its qualified message,
and picking one opens the flow on the label's id, which the editor now
treats like a click on the label: it opens the flow's settings.
The session db fixture now disposes the engine before deleting the file, since a
module under a no-op db (tests/sdk, tests/flow) may have connected first; the
sdk import tests forget the study packages they import; the supervision tests
keep the real quarantine length so it cannot expire mid-assertion.
`Run.passed` (migration d5a3c8e1f246) keeps the names the caller chose, so
the run detail and the comparison table can mark a value as the flow's own
again; rows from before it fall back to the names their params lack. Retries
hand on their parent's list, imports record their entry's.
Also: a cancel of a queued run no longer leaves its id in `_cancelled`, a
linked comparison no longer crashes before the list loads, and the pre-commit
failures are fixed (stale client, `text-[10px]`, worker formatting).
A claimed run only joined the leased set once its pipeline was built, so a
build waiting for a busy worker pool got the run swept as abandoned. Cancel
now also stops such a run, and a terminal status being overwritten is logged.
A worker over FLOW_WORKER_MAX_RSS_MB (--worker-max-rss) when its node
returns is retired instead of kept warm, in the shared pool and in every
declared one; 0, the default, keeps them all. The engine now accounts its
own memory (FLOW_RAM / --ram, else the box or the cgroup limit less 1 GB),
so a node's `ram` declaration holds locally. A declared environment the
shared pool already has is that pool rather than a duplicate set of warm
workers.
A provider set under Settings (OpenAI-compatible; key encrypted in
flow-data/ai.json) turns on an Assistant panel in the sidebar. Each turn runs
the FastMCP tools in-process as the person chatting, streams the rounds over
SSE and keeps one transcript per user. The panel floats on any screen and
shares the editor's lane: a settings panel keeps the top half, the chat the
bottom. The MCP client is now built once, for the endpoint and the chat.
A history recorded before the move (MLflow experiments) had nowhere to
go, since runs were only made by running a flow. Each entry is stored as
it ended: parameters completed and checked like a submit, cause
"import", empty commit and code digest, metrics and artifacts written
as given, nothing queued or announced. Run.external_id (new migration)
makes a second import a no-op and is an export column so a row joins
back. The request is all or nothing and capped at 200 entries;
Client.import_runs pages a longer history.
`fluksio run` walked the working directory and imported every module in
it, which ran a research repository's analysis scripts before the run was
submitted. A flow's origin now records the file and module name that
declared it, and run/sweep import just that, under the same name so the
generated node bodies do not change. A flow with no record still walks,
now skipping what .gitignore covers.
Every code-defined flow reports `flow` changed once on its next sync,
since the origin gained two fields.
A serve that lost the port race had already signed in, so client.json
named a port another engine held and every client talked to that one.
The socket is now bound first and handed to uvicorn.