| M1-67/0 |
2026-10-18 |
Base int/m1-wave39 at 8489df6, branch feat/M1-67-gateway-conformance 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 gateway part of asyncapi.yaml, openapi.yaml and actions.yaml is the same on the base apart from the translation and two later event types (device.pending, gdpr.request_due) no M1 module emits, so no contract commit was brought in. |
Instructions of the launcher, which override the brief's merge step. |
| M1-67/1 |
2026-10-18 |
G9 validates on the client side of the test harness (internal/gateway/adapters/wstest over api/specschema), not with a hook in production code; TestHarness_OnlyEntryPoint parses every _test.go file of the server and fails on a dial of github.com/coder/websocket outside the harness, so the harness stays the only entry point. |
Production frames carry no test cost and no second code path; the parser test makes a bypass visible. |
| M1-67/2 |
2026-10-18 |
Server frames are validated strictly: the bundle adds unevaluatedProperties: false to every schema with properties or allOf that declares neither additionalProperties nor unevaluatedProperties, except direct inline allOf members and definitions an allOf references directly (base schemas such as EventEnvelope). |
In 2020-12 the keyword on the composing schema sees every member's properties, on a member it would reject the siblings' fields. Clients ignore unknown fields, but an undeclared member the server sends is contract drift. |
| M1-67/3 |
2026-10-18 |
The harness keeps the existing package and its API; Dial takes the testing.TB (Dial(t, ctx, baseURL, opts...)) and registers the cleanup that drains a paused client, drops the connection and reports. There is no Start in wstest: the started api process of internal/wire and the fake-backed server of adapters/ws stay with their tests. Pause has the counterpart Unpause, because Resume is the gateway op. |
wstest exists since M1-64 (the brief's "if none exists" case does not apply). A Start in wstest would have to import internal/wire or copy the fakes; the validation does not depend on how the server was started. |
| M1-67/4 |
2026-10-18 |
The validator is github.com/santhosh-tekuri/jsonschema/v6 v6.0.3 (Apache-2.0), already in the module graph as an indirect dependency, now direct; the component api gets the vendor group jsonschema. The G8 checker contractcheck is not reused. |
The brief asks to reuse a 2020-12 validator the module already has; contractcheck validates REST responses through libopenapi and has no AsyncAPI bundle. |
| M1-67/5 |
2026-10-18 |
The frame limit of the contract defaults to the default of sync.ws_max_frame_bytes; the harness takes the value the server under test runs with (WithMaxFrameBytes). |
Several gateway worlds run with a smaller limit; the default would hide an oversized frame there. |
| M1-67/6 |
2026-10-18 |
instance emitted instance.updated (settings, branding, flags) without registering it; instance.Register now declares it and stands first in moduleRegistrations, in the order of the module register. |
The corpus test requires every M1 event type to be registered (AZ-P2); the producer is fixed, the contract was right. |
| M1-67/7 |
2026-10-18 |
wire.GatewayOpClasses names the class of every enabled op: identify, resume, heartbeat and reauth rl.gateway_connect as the contract says, focus and the typing ops as before. Only the tests read the map; heartbeat and reauth still consume nothing (M1-66/4 stays for the counting). |
The register comparison needs the class of every registered op. |
| M1-67/8 |
2026-10-18 |
The runtime register of the comparison takes the op action from routes.GatewayOps (what the dispatcher requires), the class from GatewayOpClasses, and kind and receive action of each event type from the server's catalogue (authz/domain.LookupEvent). A registered op whose action differs from asyncapi.yaml, actions.yaml or both is one problem. |
These are the sources the running server uses; the brief's fixture counts one problem for a wrong action. |
| M1-67/9 |
2026-10-18 |
The close code check covers closes the server starts; a close after Close or Abort of the client is not checked. The order check compares cursor and catchup.complete with the last durable seq delivered on that connection. |
The client's own close code is not a server frame; ready and resumed heads are snapshots, not deliveries. |
| M1-67/10 |
2026-10-18 |
The corpus scenario gets reauth_required from a token lifetime of 5m with sync.reauth_lead_time 298s and access.expiring from a worker process on the same database (authz.expiry_sweep_interval 1s, authz.expiry_reminder_lead 1h); it also lists thread.moved_out, which the move of M1-62 emits. |
Both are real producers without test hooks; the windows only shorten the waits. |
| M1-67/11 |
2026-10-19 |
Port to main: the ticket's own commits on top of the ported M1 chain, without the integration fixes INT-W42/1 to /5 (they join M1-67 with M2-07, which is not on main); the validator is v6.0.3, the version main already resolves. Docs follow the English chapters of main. |
Only the ticket's own change lands; the consumer fixes go with M2-07. |
| M1-67/12 |
2026-10-21 |
The pull request lands the port of M1-67/11 directly on main after M1-66, without M1-70, M1-71 and M1-72, which sat below it in the port chain: the gate line in docs/quality/gates.md adds G9 to the go test -race of main (G8 and the build tag contract_validate come with M1-71), and the harness part of INT-W42/1 (the client of test/harness, which only M1-70 and M1-72 bring) is left out and goes with those tickets. The ticket's own lock of the frame schema validator (httpserver.SchemaValidator serializes ValidateSchemaBytes, because libopenapi compiles schemas into its shared model on a cache miss), which came with M1-70 in the chain, is ported with this ticket, with its test TestSchemaValidator_ConcurrentUse. |
main has neither the contract harness nor the leak and performance tests; the ticket's behaviour is unchanged. |