Skip to content

Commit 3f06700

Browse files
author
Nick Franck
committed
feat(invocation-settings): require field-set transport
1 parent cc11cfe commit 3f06700

5 files changed

Lines changed: 36 additions & 13 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
body cap, the `/metadata` capability route, the request-scoped accessors, and `http_status_for`
66
— the HTTP spelling of the
77
library's normative `blame` → status rule. It sits on `utic-invocation-settings >=0.4.0`, which
8-
owns the *settings contract* — including the field-atomic wire shape, independent sealed-field
9-
resolution, and what an absent field is allowed to mean. That split is deliberate: the
8+
owns the *settings contract* — including the field-set carrier as the only accepted sealed
9+
`/invoke` shape, independent sealed-field resolution, and what an absent field is allowed to
10+
mean. That split is deliberate: the
1011
absence rule is a security decision and belongs next to the crypto it governs, while request
1112
handling and route registration belong here, where a web framework is already a dependency.
1213
Nothing about the sealed-settings wire format is decided in this repository.
@@ -38,7 +39,7 @@
3839
* **Sealed settings consumption remains opt-in.** Pass
3940
`invoke_with_sealed_dag_node_settings=True` to `wrap_in_fastapi` / `generate_fast_api` (or
4041
`--sealed-dag-node-settings` on the CLI) only for a plugin that consumes per-invoke settings;
41-
it advertises that the application accepts and acts on independently sealed settings fields.
42+
it advertises that the application accepts and acts on the versioned field-set carrier.
4243
Transport support alone continues to advertise only `invocation_settings` and
4344
`invocation_context`. A
4445
plugin that serves a custom `/metadata` payload must register it via `add_metadata_route` (which

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,4 +89,4 @@ fail_under = 15
8989

9090
[tool.uv.sources]
9191
# Temporary source pin until field-atomic utic-invocation-settings 0.4.0 is published.
92-
utic-invocation-settings = { git = "https://github.com/Unstructured-IO/utic-public-libs", subdirectory = "libs/utic-invocation-settings", rev = "733a15dd89a1696d3ac646e694f322ea06bc1eaf" }
92+
utic-invocation-settings = { git = "https://github.com/Unstructured-IO/utic-public-libs", subdirectory = "libs/utic-invocation-settings", rev = "814595faf94b424267b569c92211c093917988c4" }

test/api/test_invocation_envelope.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import pytest
1616
from fastapi import FastAPI, Request
1717
from fastapi.testclient import TestClient
18-
from utic_invocation_settings import Blame
18+
from utic_invocation_settings import FIELD_SET_FIELDS_KEY, FIELD_SET_FORMAT, Blame
1919

2020
import unstructured_platform_plugins.invocation_settings as invocation_settings_transport
2121
from unstructured_platform_plugins.invocation_settings import (
@@ -414,8 +414,15 @@ def __init__(self, blame: Blame, secret: str = ""):
414414
class TestSettingsResolutionBoundary:
415415
"""The library owns settings shape and crypto; this package owns delivery and HTTP mapping."""
416416

417-
def test_opaque_payload_is_delegated_and_final_mapping_is_bound(self, monkeypatch):
418-
opaque_payload = {"contract-owned": {"content": "opaque-to-transport"}}
417+
def test_field_set_payload_is_delegated_and_final_mapping_is_bound(self, monkeypatch):
418+
opaque_payload = {
419+
"dag_node_settings": {
420+
"format": FIELD_SET_FORMAT,
421+
FIELD_SET_FIELDS_KEY: {
422+
"api_key": {"format": "u10d.invocation-settings.v1", "opaque": "member"}
423+
},
424+
}
425+
}
419426
resolved = {"api_key": "resolved-secret", "max_characters": 700}
420427
seen = []
421428

@@ -433,6 +440,19 @@ def resolve(payload):
433440
assert seen == [opaque_payload]
434441
assert recorder.seen_settings == resolved
435442

443+
def test_empty_field_set_is_resolved_and_bound(self):
444+
field_set_payload = {
445+
"dag_node_settings": {
446+
"format": FIELD_SET_FORMAT,
447+
FIELD_SET_FIELDS_KEY: {},
448+
}
449+
}
450+
451+
recorder, response = _post_invoke({"invocation_settings": field_set_payload})
452+
453+
assert response.status_code == 200
454+
assert recorder.seen_settings == {}
455+
436456
def test_resolution_runs_off_the_event_loop(self, monkeypatch):
437457
resolver_threads = []
438458

unstructured_platform_plugins/invocation_settings.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
"""Transport for the reserved `/invoke` fields: request dependency, `/metadata`, body cap.
22
3-
The *settings contract* — including the field-atomic wire shape, sealed-field resolution, and what
4-
an absent field is allowed to mean — lives in `utic_invocation_settings`, next to the crypto it
5-
governs; every decision about a settings payload is delegated there. The *identity contract* —
3+
The *settings contract* — including the only accepted sealed `/invoke` shape (the field-set
4+
carrier), sealed-field resolution, and what an absent field is allowed to mean — lives in
5+
`utic_invocation_settings`, next to the crypto it governs; every decision about a settings payload
6+
is delegated there. The *identity contract* —
67
the `invocation_context` model — is
78
`/invoke` protocol rather than settings security and lives in this package's
89
`invocation_context` module. This module is the delivery mechanism for both: getting the payloads
@@ -127,7 +128,8 @@ def add_metadata_route(
127128
`invocation_settings` and `invocation_context` are transport capabilities: installing the
128129
dependency makes the host receive, resolve, and bind those fields. The sealed-settings
129130
capability is stronger: it tells the controller that the plugin handler consumes the resolved
130-
field-atomic settings in place of boot-time state, so it remains an explicit opt-in.
131+
field-atomic settings in place of boot-time state. The controller may therefore send the
132+
versioned field-set carrier, so this remains an explicit opt-in.
131133
132134
Last call wins: the payload lives on `app.state` and every call overwrites it, while the route
133135
is registered once. A host wrapper may register at app construction and a plugin can still

uv.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)