Skip to content

fix(api): reject non-object HTTP payloads instead of erasing them - #8

Merged
alice-viola merged 2 commits into
masterfrom
fix/reject-non-object-payloads
Sep 7, 2026
Merged

fix(api): reject non-object HTTP payloads instead of erasing them#8
alice-viola merged 2 commits into
masterfrom
fix/reject-non-object-payloads

Conversation

@albertovincenzi

Copy link
Copy Markdown
Collaborator

Problem

Both HTTP push and re-entry replaced any scalar, array, or null payload with {} before adding _gate. The request still succeeded, so callers could lose their entire application value without an error.

Fix

  • add one shared payload-shape guard for Gate's HTTP doors;
  • return 422 for values that cannot carry _gate metadata;
  • use the guard in both push and re-entry;
  • retain object payloads unchanged apart from the existing metadata fields;
  • add unit coverage for objects, nulls, scalars, strings, and arrays.

Review notes for Alice

Rejecting is intentional. Wrapping a scalar would change the payload shape consumed at egress, while forwarding it without metadata would break path ownership and bounded re-entry attempts. This PR makes the previously destructive case explicit without inventing a new wire envelope.

Direct application-owned queue ingress remains unchanged: the relay still preserves non-object messages exactly as documented.

Verification

  • cargo fmt --all -- --check
  • cargo test -p gate-server api::tests::an_http_payload_must_be_an_object_instead_of_being_discarded
  • cargo clippy -p gate-server --all-targets -- -D warnings

`#[serde(default)] payload: Value` makes an ABSENT field deserialise to
`Value::Null`, indistinguishable from an explicit one, so the new refusal
answered 422 to `{"op": "publish", "txn": "t1"}` — a push that answered 200
before this branch and queued `{"op": "publish", "_gate": {…}}` with nothing
lost. That case is outside the rationale: there is no caller data being
replaced with an empty object, because there is no caller data.

`Option<Value>` draws the distinction where serde can see it. Absent, and the
explicit `null` serde maps onto it, mean "this item carries only its envelope"
and produce `{}`. A scalar, an array or any other non-object is still refused,
which is the shape that loses something.

No push in the live suite omits `payload`, so nothing caught this.

Claude-Session: https://claude.ai/code/session_012K8u7BEJyd6nDNMCQAgH3z
@alice-viola
alice-viola merged commit 9db7272 into master Sep 7, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants