Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 22 additions & 11 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
## 0.1.0

* **This package now owns the `/invoke` transport for the reserved fields.**
`unstructured_platform_plugins.invocation_settings` holds the ASGI middleware, the `/metadata`
capability route, the request-scoped binding, and `http_status_for` — the HTTP spelling of the
`unstructured_platform_plugins.invocation_settings` holds the `/invoke` binding dependency and
body cap, the `/metadata` capability route, the request-scoped accessors, and `http_status_for`
— the HTTP spelling of the
library's normative `blame` → status rule. It sits on `utic-invocation-settings >=0.4.0`,
which owns the *settings contract* — which key carries settings, how a sealed envelope is told
from plaintext, and what an absent field is allowed to mean. That split is deliberate: the
absence rule is a security decision and belongs next to the crypto it governs, while body
buffering and route registration belong here, where a web framework is already a dependency.
absence rule is a security decision and belongs next to the crypto it governs, while request
handling and route registration belong here, where a web framework is already a dependency.
Nothing about the sealed-settings wire format is decided in this repository.
* **This package now owns the `invocation_context` identity model.**
`unstructured_platform_plugins.invocation_context` holds `InvocationContext`,
Expand All @@ -23,17 +24,27 @@
values are exposed through `current_invocation_settings()` / `current_invocation_context()`.
An absent field preserves the existing fallback behaviour; under
`FF_REQUIRE_INVOKE_WITH_SEALED_DAG_NODE_SETTINGS` missing or plaintext settings fail closed.
Repeated installation is safe: the middleware installs once and the last `/metadata`
Repeated installation is safe: the dependency installs once and the last `/metadata`
registration wins.
* **New opt-in `invoke_with_sealed_dag_node_settings` advertisement.** Pass
* **Extraction is a route dependency.** `bind_invocation_envelope` reads the body the framework
buffered and parsed (`request.json()` is Starlette-cached), so the `/invoke` body is held and
decoded exactly once per request. `install_invocation_envelope` contributes that path-aware
dependency through the router's public dependency list before `/invoke` is registered; no
private FastAPI dependency graph is mutated. It also registers the failure response shape and
installs `InvokeBodyLimitMiddleware`, a streaming byte counter that answers 413 over the cap
without buffering. Async-generator plugins explicitly re-enter the captured request binding
inside response iteration, so streaming stays correct independently of FastAPI's yield-dependency
cleanup timing.
* **Sealed settings consumption remains opt-in.** Pass
`invoke_with_sealed_dag_node_settings=True` to `wrap_in_fastapi` / `generate_fast_api` (or
`--sealed-dag-node-settings` on the CLI) only for a plugin that consumes per-invoke settings;
it advertises that the application accepts and consumes sealed per-invocation settings.
A plugin that serves a custom `/metadata` payload must register it via `add_metadata_route`
(which replaces the wrapper's route) — a plain `@app.get("/metadata")` added after construction
is shadowed by the wrapper's earlier registration.
it advertises that the application accepts and acts on sealed `dag_node_settings`. Transport
support alone continues to advertise only `invocation_settings` and `invocation_context`. A
plugin that serves a custom `/metadata` payload must register it via `add_metadata_route` (which
replaces the wrapper's route) — a plain `@app.get("/metadata")` added after construction is
shadowed by the wrapper's earlier registration.
* **Resolution runs off the event loop.** A cold resolve is an RSA unwrap of a couple of
milliseconds and this middleware fronts every invoke on the pod, so it is dispatched with
milliseconds and this dependency fronts every invoke on the pod, so it is dispatched with
`asyncio.to_thread` rather than blocking the loop.
* **Failures map through the library's blame taxonomy**, not a flat 500: only a caller-fixable
fault answers 422. Sealing drift, an envelope addressed to another recipient and a broken local
Expand Down
Loading