Skip to content

T2 / T7 — integration scenarios and the three budgets ​

Captured 2026-10-19 on feat/issue-496-gateway, from cli/ with THE_LOOP_CLI_CONFIG unset (this machine's own instance sets it; one unrelated lifecycle test reads it).

Commands and outcomes ​

text
cd cli && uv run python -m pytest -q tests/test_gateway_*_integration.py
49 passed in 7.49s
cd cli && uv run python -m pytest -q --dispatch-lag=0.5 tests/test_gateway_*_integration.py
49 passed in 19.41s

The lag run first failed one scenario (a stopped instance finds its backlog in order): it waited five seconds for three deliveries whose follow-up writes the lag slows; it now waits on the deferred outcomes being settled with a generous budget (commit 1e6e57a). Every scenario carries a Gherkin docstring with its Requirement: link.

The scenarios (the-loop scenarios --format markdown --glob 'cli/tests/test_gateway_*_integration.py') ​

#FeatureScenarioRequirementLocation
1the gateway owns the webhook routes under the providers' own contractsa signed GitHub delivery is stored, then acknowledgeddocs/specs/issue-496/requirements.md#requirement-1 (R1.3), R2.1, R2.4,cli/tests/test_gateway_adapters_integration.py:69
2the gateway owns the webhook routes under the providers' own contractsa webhook with a wrong signature is refused and stores nothingdocs/specs/issue-496/requirements.md §Security considerations, abuse case 1cli/tests/test_gateway_adapters_integration.py:103
3the gateway owns the webhook routes under the providers' own contractsan event about no work item is dropped and recorded as unbounddocs/specs/issue-496/requirements.md#requirement-2 (R2.3), R5.10cli/tests/test_gateway_adapters_integration.py:128
4every event reaches its consumers raw; the instance judgesan injected start keyword from an unauthorized actor is dropped on the instancedocs/specs/issue-496/requirements.md §Security considerations, abuse case 3;cli/tests/test_gateway_adapters_integration.py:171
5the gateway owns the webhook routes under the providers' own contractsGitHub redelivers a comment and the gateway stores one rowdocs/specs/issue-496/requirements.md#requirement-2 (R2.6)cli/tests/test_gateway_adapters_integration.py:207
6the gateway owns the webhook routes under the providers' own contractsa Jira comment through the gateway is stored under its key's subjectdocs/specs/issue-496/requirements.md#requirement-1 (R1.4), R5.1cli/tests/test_gateway_adapters_integration.py:234
7fail closeda missing secret variable leaves that ingress offdocs/specs/issue-496/design.md §Security design, fail-closedcli/tests/test_gateway_adapters_integration.py:267
8many instances, one queue eacha driver and a reviewer on two instances each receive their own queuedocs/specs/issue-496/requirements.md#requirement-4 (R4.1, R4.5), R5.3,cli/tests/test_gateway_claims_integration.py:65
9many instances, one queue eacha start by a registered actor lands on that actor's instance onlydocs/specs/issue-496/requirements.md#requirement-5 (R5.5), R4.1cli/tests/test_gateway_claims_integration.py:121
10many instances, one queue eacha pull request event with no binding is routed by actor and the instancedocs/specs/issue-496/requirements.md#requirement-5 (R5.7, R5.8)cli/tests/test_gateway_claims_integration.py:158
11many instances, one queue eacha stopped instance finds its backlog in orderdocs/specs/issue-496/requirements.md#requirement-4 (R4.4), R2.8, R3.2,cli/tests/test_gateway_claims_integration.py:195
12many instances, one queue eachthe same instance resuming after a stop receives what was routed to it meanwhiledocs/specs/issue-496/requirements.md#requirement-3 (R3.9), R4.4cli/tests/test_gateway_claims_integration.py:249
13every event reaches its consumers raw; the instance judgesan issue comment through the gateway spawns on the instance with thedocs/specs/issue-496/requirements.md#requirement-1 (R1.3), R2.1, R3.4,cli/tests/test_gateway_client_integration.py:51
14every event reaches its consumers raw; the instance judgesa crash between dispatch and commit redelivers once and the dispatcherdocs/specs/issue-496/requirements.md#requirement-3 (R3.6, R3.7)cli/tests/test_gateway_client_integration.py:99
15every event reaches its consumers raw; the instance judgesan injected start keyword from an unauthorized actor is dropped on the instancedocs/specs/issue-496/requirements.md §Security considerations, abuse case 3cli/tests/test_gateway_client_integration.py:156
16every event reaches its consumers raw; the instance judgesa Jira comment through the gateway rings the instance's doorbell unchangeddocs/specs/issue-496/requirements.md#requirement-5 (R5.1, R5.6)cli/tests/test_gateway_client_integration.py:186
17one classifier, two transportsa Slack delivery is classified by slack.dispatch_envelope on the instancedocs/specs/issue-496/requirements.md#requirement-5 (R5.6)cli/tests/test_gateway_client_integration.py:237
18instances pull their own queue from the gatewaya long-poll returns within a second of an appenddocs/specs/issue-496/requirements.md#requirement-3 (R3.3);cli/tests/test_gateway_consume_integration.py:27
19instances pull their own queue from the gatewayan empty long-poll answers empty when wait elapsesdocs/specs/issue-496/requirements.md#requirement-3 (R3.3)cli/tests/test_gateway_consume_integration.py:57
20instances pull their own queue from the gatewaya fetch by one of fifty instances reads only its own queuedocs/specs/issue-496/requirements.md non-functional Scalability (R4.1)cli/tests/test_gateway_consume_integration.py:73
21instances pull their own queue from the gatewayan event with no consumer waits on unassigned until a claim routes itdocs/specs/issue-496/requirements.md#requirement-2 (R2.2), R5.2, R5.3cli/tests/test_gateway_consume_integration.py:102
22an instance with a gateway block runs no ingress of its ownan instance with a gateway block runs no ingress of its owndocs/specs/issue-496/requirements.md#requirement-3 (R3.8), R10.3cli/tests/test_gateway_hosted_integration.py:49
23compatibilityan instance without a gateway block hosts the receiver, poller anddocs/specs/issue-496/requirements.md non-functional Compatibility; R14.2cli/tests/test_gateway_hosted_integration.py:98
24an instance with a gateway block runs no ingress of its ownGET /api/v1/gateway describes the client for the dashboard carddocs/specs/issue-496/requirements.md#requirement-10 (R10.3)cli/tests/test_gateway_hosted_integration.py:127
25an instance with a gateway block runs no ingress of its owna gateway and an instance share one machine on loopbackdocs/specs/issue-496/requirements.md#requirement-14 (R14.1, R14.2,cli/tests/test_gateway_hosted_integration.py:162
26the gateway polls the providers the instance used to pollthe gateway poller produces the same delivery ids the instance poller producesdocs/specs/issue-496/requirements.md#requirement-1 (R1.5), R7.1cli/tests/test_gateway_poll_integration.py:81
27the gateway polls the providers the instance used to polla webhook and a poll of the same comment store one rowdocs/specs/issue-496/requirements.md#requirement-7 (R7.3, R7.4)cli/tests/test_gateway_poll_integration.py:112
28the gateway polls the providers the instance used to pollan instance's commit outcome ends or keeps the presence offeringdocs/specs/issue-496/requirements.md#requirement-3 (R3.4), R1.5cli/tests/test_gateway_poll_integration.py:158
29the gateway polls the providers the instance used to polla work item that left the listing is stored as a webhook-shaped closuredocs/specs/issue-496/requirements.md#requirement-1 (R1.5)cli/tests/test_gateway_poll_integration.py:196
30the gateway is one process that follows its own configediting gateway-config.yaml starts a poller without a restartdocs/specs/issue-496/requirements.md#requirement-8 (R8.4)cli/tests/test_gateway_process_integration.py:76
31the gateway is one process that follows its own configa bad edit keeps the previous configuration runningdocs/specs/issue-496/requirements.md#requirement-8 (R8.5)cli/tests/test_gateway_process_integration.py:115
32the gateway is one process that follows its own configchanging the listen port while running is applied on the next restartdocs/specs/issue-496/requirements.md#requirement-8 (R8.6)cli/tests/test_gateway_process_integration.py:142
33fail closeda missing secret variable leaves that ingress off and says so in healthdocs/specs/issue-496/design.md §Security design, fail-closedcli/tests/test_gateway_process_integration.py:175
34fail closeda non-loopback bind without exposed refuses to startdocs/specs/issue-496/requirements.md#requirement-14 (R14.3); §Securitycli/tests/test_gateway_process_integration.py:206
35at-least-once with a bounda delivery that fails three times is dead-lettered and committeddocs/specs/issue-496/requirements.md#requirement-3 (R3.6)cli/tests/test_gateway_process_integration.py:226
36bounded growth (abuse case 6)ten thousand duplicate deliveries store one rowdocs/specs/issue-496/requirements.md §Security considerations, abuse case 6cli/tests/test_gateway_process_integration.py:256
37the lifecycle verbs mean what the service's dothe-loop gateway start, status, instances and stop run a real processdocs/specs/issue-496/requirements.md#requirement-9 (R9.1, R9.2), R14.2cli/tests/test_gateway_process_integration.py:301
38the lifecycle verbs mean what the service's doa second gateway on one state root is refused by the pidfile lockdocs/specs/issue-496/requirements.md#requirement-9 (R9.1)cli/tests/test_gateway_process_integration.py:378
39one Slack app serves every instance through the gatewaya Slack envelope is stored before it is acknowledgeddocs/specs/issue-496/requirements.md#requirement-2 (R2.4, R2.5), R1.2, R2.7cli/tests/test_gateway_slack_integration.py:123
40one Slack app serves every instance through the gatewaythe doctor's heartbeat feeds the split check, not a queuedocs/specs/issue-496/design.md §gateway/adapters (slack_socket.py)cli/tests/test_gateway_slack_integration.py:150
41one Slack app serves every instance through the gatewaya store failure withholds the ackdocs/specs/issue-496/requirements.md#requirement-2 (R2.4)cli/tests/test_gateway_slack_integration.py:165
42one Slack app serves every instance through the gatewaya Slack envelope is acknowledged within three seconds with two thousand rows storeddocs/specs/issue-496/requirements.md#requirement-2 (R2.5); testing plan T7cli/tests/test_gateway_slack_integration.py:182
43one Slack app serves every instance through the gatewaythe HTTP events route verifies the v0 signature and answers url_verificationdocs/specs/issue-496/requirements.md#requirement-7 (R7.2), R6.4cli/tests/test_gateway_slack_integration.py:224
44one Slack app serves every instance through the gatewaya replayed Slack delivery is refuseddocs/specs/issue-496/requirements.md §Security considerations, abuse case 2cli/tests/test_gateway_slack_integration.py:252
45one Slack app serves every instance through the gatewaya socket and an HTTP route on one app never double-deliverdocs/specs/issue-496/requirements.md#requirement-7 (R7.3)cli/tests/test_gateway_slack_integration.py:271
46one Slack app serves every instance through the gatewaya button press and a slash command reach the queue as interactive and slash_commandsdocs/specs/issue-496/requirements.md#requirement-5 (R5.9), R6.4cli/tests/test_gateway_slack_integration.py:290
47one classifier, two transportsdispatch_envelope hands each envelope shape to the handler the listener useddocs/specs/issue-496/requirements.md#requirement-5 (R5.6)cli/tests/test_gateway_slack_integration.py:331

T7 — the three budgets, measured ​

scratchpad/verify/t7_budgets.py drives the real store and API in-process:

Budget (testing plan)MeasuredVerdict
A Slack envelope stored and acknowledged within 3 s with 2 000 rows stored0.0284 spass
Ten thousand duplicate deliveries store one row and finish within 10 s0.692 s, 1 rowpass
A long-poll returns within 1 s of an append0.0021 spass
A fetch by one of fifty instances reads only its own queue3 rows (its backlog), 0.0037 s; query plan SEARCH d USING INDEX deliveries_queue (instance=? AND seq>?)pass

Released under the MIT License.