Inbound, identity and slots

The three rules that hand the connector something empty and expect it to cope — an empty JSON request, an envelope with nothing in it, and a well-formed slot list.

inbound.tolerates_empty_request

Checks with POST /conformance, body {}, content type application/json: InboundGateway::authenticate() returns a boolean, decode() returns an iterable, immediateResponse() returns null or a Response. None may throw.

Fails with the gateway: authenticate() did not return a boolean, decode() did not return an iterable, or the guard's threw … when a method assumed a header or a key that an empty request lacks.

Why the core's inbound gate calls these three on every request before your route runs; a gateway that throws on an unexpected body turns a platform's health check, a misrouted call or a probe into a 500 the platform retries forever.

Fix treat a missing header as "not authenticated" (return false), a body without events as "no events" (return an empty iterable), and a request that needs no special answer as null.

identity.tolerates_empty_envelope

Checks IdentityResolver::resolveInbound() given new InboundEnvelope($key, null, 'conformance', 'unknown', [], now) returns null or an IdentitySummary, nothing else, and does not throw.

Fails with resolveInbound() returned something other than null or an IdentitySummary, or the guard's threw ….

Why platforms send events with no actor (a channel post, a system notice, an event kind added after your connector shipped), and the resolver is the first thing a handler asks.

Fix read the payload defensively: no actor, no throw, return null.

identity.answers_reachability

Checks IdentityResolver::deliveryTarget() given an InstallationRef with a zero UUID and no storage ref, an empty CredentialBag and an IdentityRef for an account the connector has never seen (conformance-nobody) returns null or a Recipient, nothing else, and does not throw.

Fails with deliveryTarget() returned something other than null or a Recipient, or the guard's threw ….

Why the core asks before it sends a member anything, so an account the installation cannot reach is passed over for one it can; a resolver that throws on an unknown account would stop every member notice on the project.

Fix answer from what you already hold (your own row for the account under that installation), never by calling the platform, and return null for an account you do not know; when the platform cannot tell you in advance, return a Recipient and let the send report the refusal.

slots.well_formed

Checks UiSlots::slots(): every entry is a SlotContribution, no slot is filled twice, and every contribution has a non-empty placeholder.

Fails with slots: install is filled twice, resource_badge ships no placeholder.

Why a slot is rendered on lazy core pages, and a contribution without a placeholder breaks skeleton parity for the whole page; a slot filled twice is undefined.

Fix one contribution per slot, each naming a placeholder view. The SlotContribution constructor already refuses a contribution with both a view and a component, or with neither. An empty list passes: a connector that fills nothing is a good connector.

Empty is a valid input everywhere

These three rules share a theme the rest of your connector should follow: the core will hand you less than you expect (an event you have never seen, a request that is not from the platform, a settings form with no installation), and a connector answers "nothing here" rather than throwing.

How is this guide?

On this page

Subscriby is a product designed by you — for you.

No boardroom full of executives deciding what we ships next. Our roadmap always shaped by you with your feedback.

Share feedback or a request