| M1-66/0 |
2026-10-17 |
Base int/m1-wave38 at 297ab0c, branch feat/M1-66-gateway-focus-typing in its own worktree; the branch is not merged locally and not pushed, the integration lane ports it. Brief, spec and contract were read from origin/main; the contract of focus, typing.start, typing.stop and typing.started (asyncapi, openapi, actions.yaml) is the same on the base apart from the translation, so no contract commit was brought in. |
Instructions of the launcher, which override the brief's merge step. |
| M1-66/1 |
2026-10-17 |
A focus with an empty set succeeds without an authorize check: the handler marks the op as naming no resource, and the dispatcher's proof accepts that marker. Every other successful op still needs a check of its action. |
An empty set grants nothing and only clears the focus; there is no thread to authorize, and closing the connection with 4000 would break the documented way to clear the focus. |
| M1-66/2 |
2026-10-17 |
focus and the typing ops authorize the thread by its id first and read its space and channel from chat afterwards. Unknown and unreadable threads both get not_found from authorize; a thread that disappears between the check and the read is treated as unknown. |
02 "404 instead of 403": one code path for both. authz resolves the path itself, so the check needs no data from chat; chat only supplies the scope of typing.started and of the focus entry. |
| M1-66/3 |
2026-10-17 |
The rate limit of focus and the typing ops runs in the handler, after the dispatcher decoded the body; a malformed body closes with 4002 even when the bucket is empty. |
The dispatcher of M1-64 decodes before every handler; the order only differs for frames that close the connection anyway. |
| M1-66/4 |
2026-10-17 |
The rate-limit classes of the M1-66 ops are constants in internal/wire (GatewayOpClasses), held against x-rate-limit-class of asyncapi by TestGatewayOpRateClasses; the route generator stays unchanged. |
heartbeat and reauth carry rl.gateway_connect in the contract but must not consume it (05 section 3), so a generic "every op counts its class" would be wrong; only the three new ops count their class. |
| M1-66/5 |
2026-10-17 |
The register of ephemeral types holds the two focus-bound types typing.started and run.updated; the other ephemeral types of 05 enter it with the tickets that build them (M2-20, M3-23, M2, M6-15). |
Their candidate rules differ (one connection of the command owner, push.deliver with R-AZ-30) and are decided there; an entry now would claim a rule nobody built. |
| M1-66/6 |
2026-10-17 |
A failed admission check of an ephemeral frame drops the frame for that connection; it neither closes the connection nor retries. A failed re-check of a focused thread drops the thread. |
Ephemeral events are at most once and carry no seq, so no cursor can move past them; the durable path of the next event closes a revoked connection. Dropping a thread fails closed; the client focuses it again. |
| M1-66/7 |
2026-10-17 |
A focus entry keeps the space and channel of its thread from the time of the check; channel.archived and space.archived re-check the entries of that channel or space, thread.moved the moved thread, which takes the new channel and space of the event. Pruning changes the hub index only; the session's stored focus is re-checked on resume anyway. |
The hub has no structure of its own; the event names the affected channel or space, and the moved thread's new place is the scope of thread.moved. |
| M1-66/8 |
2026-10-17 |
occurred_at of an ephemeral event is written in UTC with milliseconds, the form of expires_at, so expires_at - occurred_at is exactly sync.typing_ttl for the client. Durable events keep their form. |
The brief asks for milliseconds in expires_at; with nanoseconds in occurred_at the difference would not be the TTL. |
| M1-66/9 |
2026-10-17 |
When the tests of the connection pass their own delivery (no hub), wire gives the gateway a publisher that relays nothing. |
The connection tests need no ephemeral delivery; production always builds the hub. |
| M1-66/10 |
2026-10-18 |
Port onto main: code and tests as in the ticket range (297ab0c..0a60ee0), which entered the integration as one squash without a conflict, except the points named here. The integration fixes INT-W41/1 to INT-W41/3 are not ported: they adapt this ticket to the agent consumers of M2-07 (no ephemeral frame before the consumer's catchup.complete, lint fixes of the union, stats.subscribe instead of ack as the still disabled op), and M2-07 is not on main, so ack stays disabled and TestGatewayOpsEnableList keeps ack. The changelog takes only the entry of this ticket (the M6-02 and M6-01 entries of the base are not on main), the module README keeps the go-arch-lint sentence of main, and TestConfigSchemaBuilds names the English chapter files. architecture/config-schema.json and api/routes/meta.gen.go are regenerated. No migration. |
The base int/m1-wave38 held tickets that are not on main (for example M2-24, M2-26, M3-13, M3-18, M4-18, M6-01); the port keeps the behaviour of the ticket and adapts only to what main has. |