Binary content upload binding
This binding is normative for the mutable draft. JSON schemas define registration and content parts, not HTTP octets, authentication, ordering, or availability. Only non-text, non-JSON media attachments use this upload binding. Ordinary text, including application-serialized structured content, MUST remain inline in event JSON; senders MUST NOT upload text or provider-native prompt JSON as a substitute for canonical messages. There is no JSON content-part kind.
Configuration and destination
Each subscription MAY configure upload: {endpoint, timeoutMs, maxBytes, auth?}.
The sender MUST POST to the exact configured endpoint, preserving its path and
query. It MUST NOT append a route, derive a URL from a content reference, or assume
that the upload endpoint shares the event receiver’s origin. It MUST NOT follow
redirects. HTTPS with receiver validation is required except for explicitly
configured loopback tests. HTTP upload is independent of event transport, including
stdio. maxBytes is a transfer limit, not permission to truncate content.
auth, when configured, uses the same explicit endpoint bindings as event delivery:
bearer (exactly one of tokenEnv or tokenRef) or oauth. See authentication bindings.
The sender MUST resolve upload credentials independently. Absence of upload.auth
uses standard OAuth protected-resource discovery on this endpoint, not event
credentials or an anonymous-only assumption. Upload authentication, discovery,
acquisition, refresh, and retries count against the upload budget, not a fresh
budget per attempt. The receiver MUST derive authorization scope from verified
credentials and authorize uploads within that scope; merely accepting a valid
token is insufficient. Event IDs, subscription IDs, and opaque refs are not
identity or authorization claims. Anonymous uploads require explicit receiver
authorization and a receiver-defined scope. Secrets MUST NOT appear in descriptor metadata.
Request framing
- Method:
POST. Content-Type: application/octet-stream.Content-Length: exact body length in octets, including zero.AHP-Content-SHA256: lowercase 64-character SHA-256 hex of the exact body.
The body MUST be raw arbitrary octets. It MUST NOT be JSON-wrapped, base64-encoded,
converted through UTF-8, compressed, or otherwise content-encoded. This binding
does not use chunked transfer. bodyBase64 in a test fixture is test storage only;
clients decode it once before counting, hashing, and sending bytes.
Confirmation and errors
The sender supplies neither a content reference nor subscription identity. The
receiver allocates an opaque immutable reference after validating and authorizing
the bytes. Only 201 Created with Content-Type: application/json and a canonical
content-upload-receipt JSON body
{"ref": "receiver-allocated-reference", "size": 3, "sha256": "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"}
confirms synchronous availability within the authorized scope. Before publishing
an event, the sender MUST validate this receipt and verify that size and
sha256 match the exact bytes sent. It MUST use the returned ref, not construct
one. A missing, malformed, or mismatched receipt fails the upload. 202 and
204 are not confirmation. The receiver uses:
| Status | Meaning |
|---|---|
| 400 | Missing/malformed framing or a length/hash mismatch |
| 401 | Missing or invalid credentials |
| 403 | Principal is not authorized for the upload scope |
| 404 | Unknown upload route |
| 413 | Receiver size limit exceeded |
Other statuses fail the upload. An event receiver MAY return 404 when referenced content is unavailable. The 201 receipt is an upload confirmation, not an event acknowledgement. No renewal, expiry negotiation, or protocol-level retrieval route is introduced.
The receiver MUST retain confirmed bytes for event processing. References are immutable and access is controlled by credential-derived scope. A retry MAY allocate another reference, including after a lost response. Senders MUST NOT probe caller-selected references or rely on reference-based idempotence. A receiver MUST NOT change the bytes bound to an allocated reference. Changed bytes require a new upload and reference from the producer, not an attachment-content modification effect. An effect cannot mutate or rebind an existing attachment.
Upload before publication
For both hooks/intercept and hooks/observe, the sender MUST finish every
selected, authorized upload before publishing its referring message. Failure MUST
NOT yield a ready body reference. Apply the boundary’s failure policy, suppress
failed delivery, or use an explicit schema-valid content gap. Never substitute
inline bytes, a local path, or an arbitrary retrieval URL. Selection is not
authorization; permissions are checked before transfer. Observations remain best
effort and a failed upload MUST NOT reopen settlement.
Attachment preparation is distinct from interceptor execution. A sender MAY materialize immutable attachments once and prepare their authorized uploads to matching subscriptions concurrently before the interceptor chain. This does not permit concurrent interceptor calls or advance a later subscription’s failure policy ahead of its turn. Upload failures remain associated with the affected delivery; they do not invalidate confirmed uploads for independent receivers. An accepted edit may remove an attachment after its upload has completed; the sender MUST NOT publish that removed attachment in the effective event. A confirmed reference MAY be reused for the same immutable attachment and receiver authorization context in a later delivery, including observation after settlement; it MUST NOT be reused across independent authorization scopes. All preparation remains subject to resource limits, deadlines, and cancellation.
Canonical messages and content selection
AHP-CONTENT-001 — MUST. Model-visible content uses canonical message roles and ordered parts; ordinary text stays inline, structured content is serialized into text, and no JSON part is permitted.
AHP-CONTENT-002 — MUST. Authorization and body/metadata/omit selection apply to inline text and attachments without duplicate-content bypass; only immutable non-text, non-JSON media attachments use uploads.
A canonical message has id, role, and an ordered parts list of content
parts, with an optional boolean synthesized identity marker. Roles are system,
developer, user, assistant, and tool; provider-native role names MUST be
normalized. Role belongs to the message, not to a part or an uploaded blob.
Message order and part order are significant and MUST be preserved. Model-visible
event slots use the shared message definition (modelVisibleItem is an alias),
not provider-native message objects or uploaded prompt JSON. Generic content
slots use parts without inventing a conversation role.
Each part has id, kind (text or attachment), mediaType, and selection.
A text part has mediaType: "text/plain"; a body-selected text part carries a
text string directly in event JSON.
Applications that need structured content MUST serialize it into that string;
there is no JSON part and no protocol interpretation or merge of structure
serialized inside text. Text MUST NOT be replaced with an upload reference.
Reasoning and skills are content categories, not additional part kinds; reasoning
remains within its owning assistant message and distinguishable by category.
A body-selected attachment has a non-text, non-JSON mediaType and body: {ref}.
Attachment media types MUST NOT be text/*, application/json, or JSON-derived
media types (including +json types).
Attachments are immutable. A message-list effect MAY carry unchanged, authorized
existing attachment references, but MUST NOT edit or merge the bytes behind a
reference. Removing or reordering a message or part does not mutate its referenced
attachment. There is no binary modification target or path. Changed attachment
bytes require a new authorized upload and reference at the producing boundary,
not an edit through a modification effect. Upload confirmation size and sha256
belong to the separate receipt, not the ready body reference or ready part.
The receiver resolves the reference within its authorized scope and retains the
verified size and digest with it.
Content selection uses a required default plus category-name keys whose values
are body, metadata, or omit. Unknown categories use the default. Explicit
category overrides media-derived selection. Producers MUST preserve known
reasoning classification as category: "reasoning"; renaming a category MUST NOT
bypass its authorization or selection policy. Selection and authorization apply
to inline text as well as attachments: metadata/omit parts MUST NOT carry text
or body; a missing requested body uses selection: "body" and gap instead.
These views may disclose optional size/hash metadata only under the selected
policy. They do not imply body availability. Metadata/omit selection MUST NOT
cause an upload or consume an attachment source. Missing requested bodies follow
the boundary failure policy; intentional metadata/omit selection is not failure.
Synthesized message and part IDs MUST carry synthesized: true; the marker is
boolean. All SHA-256 fields are exactly 64 lowercase hexadecimal characters.
A sender MUST reject CR/LF in framing values before constructing HTTP headers.
Preparation and security boundaries
For attachments, select independently authorized receivers and content views before consuming an owned body source. Constructing an owned source MUST NOT read or upload its bytes. Metadata-only, omitted, and unmatched routes MUST NOT consume the source. For selected body delivery, snapshot the bytes once within byte and time bounds, compute their actual size and SHA-256, and reuse that immutable snapshot for authorized fan-out rather than racing readers. Each receiver still allocates and confirms its own independently authorized upload reference before dependent event publication. Optional caller-supplied size/hash expectations MUST be checked against actual bytes, not trusted as verification; they are not required to construct a source.
Owned sources and any spool resources have explicit cleanup on success, failure, cancellation, and unused-source paths. Snapshotting and staging remain within the owning operation; they MUST NOT continue as detached work after completion. Host execution data remains separate from a delivery source. Bounded spooling is permitted; unbounded buffering and silent truncation are not.
The configured transfer budget is separate from the per-interceptor timeout, which starts at interception dispatch. Both are bounded by any enclosing hook-call budget, including pre-dispatch preparation. Transfer failure is distinct from hook failure; content gaps describe withholding, unavailability, limits or transfer failure without silent truncation. Observation preparation and uploads are best effort, never an execution gate or a durable buffering requirement.
Authorization and selection apply to duplicate bodies in native, input, output
and other opaque JSON, not only normalized descriptors. Producers must project
those payloads explicitly; schemas cannot identify every embedded secret or
model-visible byte. includeNative is off by default; approved native values may
be any JSON value, without an invented wrapper, and never replace required
normalized fields. Event authentication, client certificates and token acquisition
must not migrate to an upload resource merely because event delivery uses them.
An upload confirmation is not an event acknowledgement or proof of durable storage.