Baler Derivation API
CRUD for derivations - definitions of synthetic/computed histories,
computed via Baler's own rule engine. A derivation names a
SPARQL query that selects what to run over and a Python function (the
"lambda") that computes the value; the result is written back as a new
point in point_history and attached to its target entity in the
ontology, with full prov:wasDerivedFrom lineage to its inputs.
This is a separate process/port from the ingest API (ingest_api.py,
openapi.yaml) and the fault API (fault_api.py,
fault-api-openapi.yaml) - each API's spec is served by the process it
actually describes.
The actual evaluation (running derivations on a sweep and, for
cur-mode derivations, on matching MQTT messages) happens in a separate
process, derivation_engine.py, which reads the same derivations.json
file this API writes - there's no direct connection between this API
and the engine beyond that shared config.
Security: POST /derivations, POST /derivations/test, and
POST /derivations/dry-run execute submitted Python. fn_source is
compiled and run through a restricted sandbox (AST denylist blocking
imports, eval/exec/__import__/dunder-attribute access, plus a
builtins allowlist, plus a wall-clock timeout) - this is defense in
depth, not a hard isolation boundary. Only OS-level isolation
(a subprocess or container per execution) would be that, and it's out
of scope for v1. Do not expose this API to untrusted callers or past a
fully trusted, localhost-only deployment boundary - the same posture
fault-api-openapi.yaml already documents for webhook URLs, one notch
more dangerous (arbitrary code execution, not SSRF).

