Hookway

Request-pipeline invariants

These rules protect the request and delivery boundaries. They are checked by configuration validation, the graph compiler, and runtime tests. Port types prevent incompatible phases from being connected; they do not require a flow to include every optional protection node.

The numbered rules correspond to TestI* tests in internal/conformance. Run make conformance after changing request ordering, delivery, or capture. Change a guarantee deliberately: update the implementation, explanation, and relevant tests together.

Port model

PortCarriesProduced by
PortNoneNo input; starts a top-level flow—
PortRequestHeadMethod, path, query, headers, source IP, and request metadata; no buffered bodyhttp_trigger and request-head gates
PortRawRequestRequest head and original, unverified body bytesread_body
PortVerifiedOriginal request after the configured auth decisionauth, explicit no_auth, verified quotas, and replay_guard
PortEnvelopeOriginal request, parsed body, and variables accumulated by later nodesenvelope and envelope-processing nodes
PortTerminalResponse or delivery result with no outgoing payloadrespond or a terminal delivery

no_auth deliberately permits unverified traffic. Its Verified port means the flow made an explicit authentication decision, not that a signature was checked. It supplies no trusted identity for verified quotas.

A top-level flow has exactly one trigger and no cycles. Batch child flows instead start with an envelope and have no trigger. Leaves must be terminal or a publish node with no success continuation. A publish continuation carries an envelope. Only publish nodes accept multiple incoming edges. Each arriving path executes the publish and its applicable continuation independently; payloads are not merged. Attempted and suppressed state is local to an execution path, so a skipped branch cannot hide another arrival at a shared destination. See Configuration for node and edge syntax.

PortType.Accepts in internal/graph/port.go uses strict equality. Keep that boundary: allowing RawRequest to stand in for RequestHead would let gates run after the body was buffered. Error edges carry the failing node's input; their target must accept that input phase.

Request ordering

IDRuleEnforcement and scope
I1Loop-hop checks precede body buffering.loop_guard takes a request head and stops a detected loop before read_body.
I2In-flight admission precedes body buffering.The server acquires capacity before executing the flow.
I3IP allowlists precede body buffering.An ip_allowlist node only accepts a request head.
I4Request-head rate limits precede body buffering.The default stage uses a request head. Explicit stage: verified quotas instead follow authentication and use trusted identity.
I5Body-derived business work follows the auth decision.Parsing an envelope requires Verified; use no_auth only when unauthenticated access is intentional.
I6Body signatures use the original request, before business transforms.Raw bytes are the default. The explicit hmac_sha256_headers_dot / body_representation: json_js protocol signs a JavaScript JSON representation of that original body. Static tokens and Basic auth do not sign the body.
I7Duplicate reservations follow the auth decision and precede envelope parsing.replay_guard consumes and returns Verified.
I8A branch filtered out before a lookup performs no lookup.Graph execution stops that branch. When a flow contains filters and lookups, configuration validation requires a filter before each lookup path. Types alone do not enforce this ordering.

Signature formats and their coverage are explained in Security. Rate limits and duplicate reservations require an explicit protection store.

Execution and recovery

These rules depend on node and executor behavior, not just port types.

IDRule
I10Publish detaches from caller cancellation with context.WithoutCancel, bounded by publish_timeout. respond still uses the caller's connection.
I10aA publish success edge runs only after success, using the same detached timeout budget. Edge-list order is not a delivery dependency; respond cannot follow a publish continuation.
I11Unknown authentication schemes fail validation and standalone startup. Verification errors never grant access.
I12A filter whose conditions cannot compile matches nothing; it cannot accidentally deliver an event. Validate configurations before serving to catch the error.
I13A per-flow graph compilation error keeps that configured path registered with a 500 response. Startup-wide errors, such as invalid auth or store configuration, can refuse the whole server.
I14A filtered branch is not counted as a failed delivery.
I15Best-effort failure capture does not change the caller's response or readiness. Bounded background workers build/upload records; capture can be dropped. Protection-store readiness is a separate rule.
I16A handler panic is recovered and returns 500 instead of dropping the connection.
I17A duplicate reservation is released only by its owner when no delivery side effect occurred. Successful, partial, or uncertain effects retain it.
I18Secret/digest comparisons use hmac.Equal or subtle.ConstantTimeCompare. The conformance check is structural, not a timing benchmark.
I19Nodes leave their input unchanged and return a changed copy. Failure capture depends on retaining the input that the failed node actually received.
I20Failure capture retains eligible failures only after a final 2xx response and when no successful error path or dead-letter delivery handled them. Async webhook capture waits for the final inbound status. Explicit Inspect observations freeze current body/context, survive later failures/non-2xx responses and never contribute delivery side effects.
I21A redrive executes at most once per run and does not collect inspections, capture, dead-letter, or compensate its own failure. Core reads the full control request before execution. Replay resubmits only when core explicitly reports not_started, or the request was incomplete before execution could begin. Destination retry settings can still make several delivery attempts.

See Delivery guarantees for acknowledgement policies and Replay service for captured retries.

Limits to plan around

IDLimitWhat it means
L1Protection depends on its store.Memory is per process. Redis/PostgreSQL coordinate replicas, but expiry, restart, or backend data loss can admit repeats. Consumers still need stable sender event IDs and idempotency.
L2In-flight work is not durably resumable.A crash loses unfinished work and async memory-queue jobs. Sender redelivery or a retained replay record may recover some failures; neither covers every acknowledged event.
L3One process has access to its configured destination credentials.Named connections organize credentials but do not isolate flows. Split deployments by trust domain when needed.
L4Inbound servers use plain HTTP.Terminate TLS at your ingress, load balancer, or sidecar.
L5The JavaScript runtime has no hard memory cap.Use bounded code and deployment memory limits; see the code-node security guidance.