@crvouga/mockingbird-service-aws-iot 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # Changelog — @crvouga/mockingbird-service-aws-iot
2
+
3
+ ## 0.1.0 (2026-10-11)
4
+
5
+ Initial release.
package/DISCOVERY.md ADDED
@@ -0,0 +1,55 @@
1
+ # @crvouga/mockingbird-service-aws-iot discovery
2
+
3
+ This is the installed-package index for coding agents and tooling. All relative links resolve
4
+ inside `node_modules/@crvouga/mockingbird-service-aws-iot/`; no repository checkout is needed to discover the emulator's
5
+ supported surface or documented behavior.
6
+
7
+ ## Capability and behavior sources
8
+
9
+ | Question | Authoritative file | What it contains |
10
+ | --- | --- | --- |
11
+ | Behaviour and integration | [`README.md`](README.md) | Routes, state transitions, auth, webhooks, controls, presets and deliberate omissions. |
12
+ | Exact capabilities | [`SUPPORT.md`](SUPPORT.md) | Supported, unsupported and parity-covered operations or commands, including reasons for gaps. |
13
+ | Wire contract | [`openapi.yaml`](openapi.yaml) | Machine-readable paths, methods, schemas, responses and parity annotations. |
14
+ | Public API | [`dist/index.d.ts`](dist/index.d.ts) | The installed package's exact TypeScript exports and signatures. |
15
+ | Package metadata | [`package.json`](package.json) | Runtime/entry-point claims, vendor links, parity scope/tier and `mockingbird.discovery`. |
16
+
17
+ Read these together: the contract/capability matrix says *what* is available, while the README
18
+ defines stateful behavior, lifecycle rules, test controls, and intentional oracle differences.
19
+ If prose and an executable surface disagree, report a parity mismatch instead of adding a
20
+ consumer-side workaround.
21
+
22
+ ## Parity and oracle
23
+
24
+ - Declared parity surface: **HTTP Publish with SigV4, MQTT 5 sessions and custom-authorizer policies**.
25
+ - Parity tier: **cold** (the repository controls when live checks run).
26
+ - Oracle: **Live vendor API or sandbox**.
27
+ - Repository command: `bun run parity:service -- aws-iot`.
28
+ - Evidence model: Run from a Mockingbird checkout; credentials come only from .env.local or GitHub Actions secrets. Missing credentials exit 2.
29
+
30
+ The npm package contains evidence summaries and the exact contract, not credentials or the
31
+ repository-only parity harness. Self-parity/property and acceptance tests run in the Mockingbird
32
+ repository; live parity is an additional oracle check, not a substitute for the packaged matrix.
33
+
34
+ ## Runtime introspection
35
+
36
+ - `GET /__admin/health`
37
+ - `GET /__admin`
38
+ - `GET /__admin/state`
39
+ - `GET /__admin/requests`
40
+ - `GET /__admin/metrics`
41
+ - `GET /__admin/faults/presets`
42
+ - `GET /__admin/ui`
43
+
44
+ For HTTP services, use `x-mockingbird-namespace` (or the documented credential/path carrier) so
45
+ parallel tests do not share state. Admin state, journal, metrics and fault-preset endpoints are
46
+ designed for assertions and diagnosis by consuming test suites.
47
+
48
+ ## Report a mismatch or missing capability
49
+
50
+ Follow the [agent reporting contract](https://github.com/crvouga/mockingbird/blob/main/docs/REPORTING_ISSUES.md). Include package version,
51
+ operation/command, a minimal redacted request, actual emulator result, expected oracle result or vendor
52
+ documentation, and whether the mismatch appears in the matrix. Never include keys, tokens,
53
+ customer data, prompts, PHI, card data, or unredacted recordings.
54
+
55
+ Service key: `aws-iot`.
package/README.md CHANGED
@@ -1,3 +1,235 @@
1
- # Temporary Holding Version
1
+ # @crvouga/mockingbird-service-aws-iot
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ > Local emulators. Real API contracts. Part of [Mockingbird](https://github.com/crvouga/mockingbird).
4
+
5
+ Stateful emulator of the **AWS IoT Core data plane** for test suites and local development:
6
+ IoTDataPlane `Publish` over HTTP with Signature Version 4 verification, and the MQTT 5 message
7
+ broker over TCP and WebSocket (`/mqtt`) with custom-authorizer policies, AWS IoT's persistent
8
+ session rules and its documented departures from the MQTT specification. It runs in the test
9
+ process on OS-assigned ports with no AWS account, and session expiry follows a clock the suite
10
+ moves instead of waiting.
11
+
12
+ - Operation coverage (HTTP): [SUPPORT.md](https://github.com/crvouga/mockingbird/blob/main/packages/service/aws-iot/SUPPORT.md)
13
+ - No AWS account was used as an oracle. Behaviour follows the
14
+ [Publish API reference](https://docs.aws.amazon.com/iot/latest/apireference/API_iotdata_Publish.html),
15
+ the [MQTT](https://docs.aws.amazon.com/iot/latest/developerguide/mqtt.html),
16
+ [custom authentication](https://docs.aws.amazon.com/iot/latest/developerguide/custom-auth.html),
17
+ [policy](https://docs.aws.amazon.com/iot/latest/developerguide/pub-sub-policy.html) and
18
+ [quota](https://docs.aws.amazon.com/general/latest/gr/iot-core.html#message-broker-limits)
19
+ pages, and AWS's SDK signer. What those do not settle is listed under [Unverified](#unverified).
20
+ - It is not the EMQX emulator with another name: where the two vendors differ
21
+ (`@crvouga/mockingbird-service-emqx`), each follows its own.
22
+
23
+ ## Install
24
+
25
+ ```bash
26
+ npm install -D @crvouga/mockingbird-service-aws-iot
27
+ ```
28
+
29
+ ESM only. Node >= 22 or Bun >= 1.2. No native dependencies. Serve it with
30
+ `npx mockingbird-aws-iot serve` or `createServer` from `./server` (Node and Bun). `createRuntime`
31
+ (the default entry) is portable: it serves `Publish` over `fetch` and MQTT through
32
+ `runtime.attach`, with no listener.
33
+
34
+ ## Usage
35
+
36
+ Point the app's IoT data endpoint at `url` and its MQTT URL at `mqttUrl` or `wsUrl`. There is no
37
+ TLS: use the `http://`, `mqtt://` and `ws://` URLs as given.
38
+
39
+ ```ts
40
+ import { createServer } from "@crvouga/mockingbird-service-aws-iot/server"
41
+ import { IoTDataPlaneClient, PublishCommand } from "@aws-sdk/client-iot-data-plane"
42
+ import mqtt from "mqtt"
43
+
44
+ const arn = (resource: string) => `arn:aws:iot:us-east-1:123456789012:${resource}`
45
+ const iot = await createServer({
46
+ settings: {
47
+ // HTTP: SigV4 signatures are verified against these keys.
48
+ credentials: [{ accessKeyId: "test-key", secretAccessKey: "test-secret" }],
49
+ // MQTT: what the custom authorizer answers for a username and password.
50
+ authorizers: [
51
+ {
52
+ username: "alice",
53
+ password: "alice-token",
54
+ policyDocuments: [
55
+ {
56
+ Version: "2012-10-17",
57
+ Statement: [
58
+ { Effect: "Allow", Action: "iot:Connect", Resource: arn("client/alice-*") },
59
+ { Effect: "Allow", Action: "iot:Subscribe", Resource: arn("topicfilter/user/alice") },
60
+ { Effect: "Allow", Action: "iot:Receive", Resource: arn("topic/user/alice") },
61
+ ],
62
+ },
63
+ ],
64
+ },
65
+ ],
66
+ },
67
+ })
68
+
69
+ const device = await mqtt.connectAsync(iot.mqttUrl, {
70
+ protocolVersion: 5,
71
+ clientId: "alice-phone",
72
+ clean: false,
73
+ properties: { sessionExpiryInterval: 600 },
74
+ username: "alice",
75
+ password: "alice-token",
76
+ })
77
+ await device.subscribeAsync("user/alice", { qos: 1 })
78
+
79
+ const client = new IoTDataPlaneClient({
80
+ region: "us-east-1",
81
+ endpoint: iot.url,
82
+ credentials: { accessKeyId: "test-key", secretAccessKey: "test-secret" },
83
+ })
84
+ await client.send(new PublishCommand({ topic: "user/alice", qos: 1, payload: '{"hello":"alice"}' }))
85
+
86
+ await device.endAsync()
87
+ await iot.close() // closes every listener and drops every client
88
+ ```
89
+
90
+ ```bash
91
+ npx mockingbird-aws-iot serve --port 8851 --mqtt-port 8883 \
92
+ --credential test-key:test-secret --settings ./aws-iot-settings.json
93
+ ```
94
+
95
+ Each `createServer()` is its own broker with its own ports; two of them share nothing. Under
96
+ Bun, `mqtt`'s Node WebSocket transport does not start (Bun's `ws` has no
97
+ `createWebSocketStream`), so connect to `wsUrl` with the library's browser transport
98
+ (`new MqttClient((c) => browserStreamBuilder(c, options), options)` from `mqtt/lib/connect/ws`),
99
+ or use `connectStream(runtime)` from `./server` for a socket-free connection. On Node the plain
100
+ `mqtt.connect("ws://…/mqtt")` works.
101
+
102
+ Topics are what the client sends. AWS IoT separates levels with `/` and gives `:` no meaning,
103
+ so an application that calls a topic `user:example` and publishes it as `user/example` does that
104
+ mapping itself; the emulator, like AWS, never sees the first form.
105
+
106
+ ### Routes
107
+
108
+ | Route | Behaviour |
109
+ | --- | --- |
110
+ | `POST /topics/{topic}?qos=0\|1` | IoTDataPlane `Publish`. The body is the payload, taken as the bytes sent. The topic is percent-decoded once (`user%2Fexample` is `user/example`; `a%252Fb` is the literal `a%2Fb`); unencoded slashes are accepted too, as in AWS's own examples. `200 {"message": "OK", "traceId": "…"}` whether or not anyone subscribes. Also read: `retain`, `contentType`, `responseTopic`, `messageExpiry` (adjusted into 1…604800), and the `x-amz-mqtt5-user-properties`, `x-amz-mqtt5-payload-format-indicator` and `x-amz-mqtt5-correlation-data` headers, which reach subscribers as MQTT 5 properties. |
111
+ | errors | `{"message": "…", "traceId": "…"}` with `x-amzn-ErrorType`. `400 InvalidRequestException` for a topic that is empty, has wildcards, exceeds 256 bytes or 7 slashes, or starts with `$`; a `qos` other than 0 or 1; a bad `retain`, `messageExpiry` or MQTT 5 header; a malformed percent escape; a payload over 128 KB. `401 UnauthorizedException` when the signing identity's policy does not allow `iot:Publish` on the topic. `403` (`Forbidden`) when the request is unsigned or its signature does not verify. `405 MethodNotAllowedException` for any other verb or path. An `x-mockingbird-reason` header says which check failed. |
112
+
113
+ **SigV4.** With `credentials` empty, any request carrying a well-formed
114
+ `Authorization: AWS4-HMAC-SHA256 …` header is accepted unverified (as Mockingbird's other AWS
115
+ emulators do), and an unsigned request is still refused. With credentials configured the
116
+ signature must verify: service `iotdata`, the configured region, `host` and `x-amz-date` signed,
117
+ the path encoded a second time in the canonical request (as every service but S3), the payload
118
+ hash the SHA-256 of the body, and `x-amz-security-token` matching for temporary credentials.
119
+ A credential's `policyDocuments`, when given, must allow `iot:Publish` on
120
+ `arn:aws:iot:<region>:<account>:topic/<topic>`.
121
+
122
+ ### MQTT
123
+
124
+ MQTT 5.0 over TCP and over WebSocket at `/mqtt` (subprotocol `mqtt`). Both reach the same
125
+ sessions, and the same sessions `Publish` delivers to.
126
+
127
+ | Area | Behaviour |
128
+ | --- | --- |
129
+ | Authentication | Custom authentication only. `authorizers` lists the outcomes of a custom authorizer by MQTT username and password (the username's `?x-amz-customauthorizer-name=…` query is read and stripped); or pass `authorizer` to `createRuntime`, a function that receives the Lambda event (`protocolData.mqtt.{username, password (base64), clientId}`) and returns `{isAuthenticated, principalId, policyDocuments}`. No matching entry, `isAuthenticated: false`, a failing function, or a policy that does not allow `iot:Connect` on `client/<clientId>` is CONNACK `0x87`. With neither configured every client connects, allowed everything. |
130
+ | Policies | `iot:Connect` on `client/<id>`, `iot:Publish` and `iot:Receive` on `topic/<name>`, `iot:Subscribe` on `topicfilter/<filter>`. Default deny; an explicit `Deny` overrides any `Allow`. `*` and `?` are wildcards (`*` spans levels); MQTT's `+` and `#` in a policy are ordinary characters, so a policy must name the filter a client subscribes with. `${iot:ClientId}` expands. A denied SUBSCRIBE is SUBACK `0x87`; a denied QoS 1 PUBLISH is PUBACK `0x87`; a subscriber without `iot:Receive` for a topic silently receives nothing from it. |
131
+ | CONNACK | Maximum QoS 1, Retain Available 0 (see below), Maximum Packet Size 149504, Topic Alias Maximum 8, Receive Maximum 100, Wildcard Subscription Available 1, Subscription Identifier Available 0, Shared Subscription Available 0. Server Keep Alive when AWS would change the client's: 1200 for `0` or more than 1200, 30 for less than 30. |
132
+ | Persistent sessions | Clean Start 0 with a Session Expiry Interval above 0. The interval is capped at the account quota (`persistentSessionExpirySeconds`, one hour by default, seven days at most) and the adjusted value is returned in the CONNACK. A session keeps its subscriptions and QoS 1 messages only (QoS 0 is not stored). The timer starts when the connection ends; at expiry the session and its queue are discarded. |
133
+ | Client id | At most 128 bytes (CONNACK `0x85` beyond). A second connection with a connected client id is accepted and the first is sent DISCONNECT `0x8E`. |
134
+ | Topics | At most 256 bytes and 7 forward slashes; topics and filters beginning with `$` are refused (SUBACK `0x8F`, PUBACK `0x90`). `sensor/#` matches `sensor/` and `sensor/a/b` and not `sensor`. |
135
+ | QoS | 0 and 1. A SUBSCRIBE asking for QoS 2 gets no SUBACK; a QoS 2 PUBLISH is answered DISCONNECT `0x9B`. A publish nobody subscribes to is acknowledged `0x00`. |
136
+ | Payload | At most 128 KB; a larger PUBLISH disconnects the client. |
137
+ | Subscription Identifiers | Not supported, as on AWS: DISCONNECT `0xA1`. |
138
+
139
+ ### Admin (beyond the standard contract)
140
+
141
+ | Route | Effect |
142
+ | --- | --- |
143
+ | `GET /__admin/publishes` | `{total, publishes}`: the last 1000 accepted publishes as metadata (`topic`, `qos`, `bytes`, `source: "http" \| "mqtt" \| "admin"`, `clientId`, `matched`, `at`). Payloads are never kept. |
144
+ | `GET /__admin/clients`, `GET /__admin/clients/:clientId` | Sessions: `connected`, `transport`, `sessionExpiryInterval`, `expiresAt`, counts of `subscriptions`, `queued`, `inflight`; one session also lists its `topics`. |
145
+ | `GET /__admin/subscriptions?clientId=` | Subscriptions: `topicFilter`, granted `qos`, options. |
146
+ | `POST /__admin/inject` | `{topic, payload \| payloadBase64, qos?: 0 \| 1}`: deliver raw bytes to the topic's subscribers, bypassing signing, validation and publish policy (`iot:Receive` still applies). Use it for bytes a consumer's parser must refuse. Answers `{matched}`. |
147
+ | `POST /__admin/transport/cut` | `{clientId?}`: a network outage for one client, or for all. Live connections die with no DISCONNECT and new CONNECTs are dropped unanswered. Sessions are untouched and expire on their own schedule. |
148
+ | `POST /__admin/transport/restore` | `{clientId?}`: end the outage. `GET /__admin/transport` lists what is cut. |
149
+ | `POST /__admin/clients/:clientId/disconnect` | `{reasonCode?}`: send DISCONNECT (default `0x98`) and close; the session stays. |
150
+ | `GET` / `PUT /__admin/settings` | `{credentials?, authorizers?, region?, accountId?, persistentSessionExpirySeconds?}` for the calling namespace. A policy document the emulator cannot evaluate is refused with `400`. Reset returns to the values the runtime was created with. |
151
+ | `POST /__admin/clock` | Standard. Moving the clock settles session expiry at once. |
152
+ | `POST /__admin/reset`, checkpoints | Standard. Reset drops every connection and session. Restoring a checkpoint restores sessions, subscriptions and queues, and drops live connections. |
153
+
154
+ Fault presets (`POST /__admin/faults {"preset": "<name>", "count"?: n}`; `GET /__admin/faults/presets`):
155
+ `publish_throttled` (`429 ThrottlingException`), `publish_unauthorized` (`401
156
+ UnauthorizedException`), `publish_internal_failure` (`500 InternalFailureException`),
157
+ `connect_not_authorized` (CONNACK `0x87`), `connect_bad_credentials` (`0x86`),
158
+ `connect_quota_exceeded` (`0x97`). A CONNECT is the fault operation `MqttConnect`, so any reason
159
+ code can be forced: `{"operationId": "MqttConnect", "effect": "connack", "params": {"reasonCode": 137}}`.
160
+
161
+ ### Namespaces
162
+
163
+ Parallel workers isolate themselves by namespace. HTTP: `x-mockingbird-namespace`, a
164
+ `/__admin/ns/<name>` prefix on the endpoint, or by access key id
165
+ (`PUT /__admin/credentials {"credentials": {"<AWS_ACCESS_KEY_ID>": "<namespace>"}}`). MQTT: the
166
+ WebSocket URL `ws://…/__admin/ns/<name>/mqtt`, or the same credential map keyed by the CONNECT's
167
+ username (without its query string), then its client id. Unmapped clients use the default
168
+ namespace.
169
+
170
+ ### Unverified
171
+
172
+ The documentation does not settle these; the emulator's choice is stated so a suite does not
173
+ mistake it for the vendor's:
174
+
175
+ - The success body. The API reference says `Publish` answers an empty body; AWS's custom
176
+ authentication tutorial shows `{"message": "OK", "traceId": "…"}`. The emulator sends the
177
+ latter, which the SDK ignores.
178
+ - Refused credentials. For an unsigned request or a signature that does not verify the emulator
179
+ answers `403 {"message": "Forbidden", "traceId"}` with `x-amzn-ErrorType: ForbiddenException`:
180
+ the body is the one AWS documents for a rejected custom-authorizer signature, the status and
181
+ header are inferred. For a valid signature without `iot:Publish` it answers the documented
182
+ `401 UnauthorizedException`; whether AWS answers 401 or 403 there was not confirmed.
183
+ - Whether AWS sends `x-amzn-ErrorType`, the `message` text of each `400`, and the HTTP error for
184
+ a payload over 128 KB (the emulator uses `400 InvalidRequestException`).
185
+ - "128 KB" and "146 KB" are taken as 131072 and 149504 bytes. AWS's MQTT page also says the
186
+ Maximum Packet Size "cannot exceed 128 KB"; the quota page's 146 KB is used.
187
+ - What the client sees for `isAuthenticated: false` (AWS says it "terminates the connection";
188
+ the emulator sends CONNACK `0x87` first), for a topic over the limits or beginning with `$`
189
+ (AWS says such operations "can result in a terminated connection"; the emulator answers
190
+ SUBACK `0x8F` / PUBACK `0x90` and keeps the connection), and for an oversized MQTT payload
191
+ (DISCONNECT `0x95`).
192
+ - A QoS 2 SUBSCRIBE getting no SUBACK is AWS's documented behaviour, stated without a protocol
193
+ version; it is applied to MQTT 5 as written.
194
+ - The Receive Maximum (100, from the quota on unacknowledged publishes), when Server Keep Alive
195
+ is sent, the format of an assigned client id, and whether the Lambda event's `username`
196
+ includes the query string (the emulator strips it).
197
+ - Signing dates are not checked against the clock, so a suite can freeze time.
198
+
199
+ ### Deliberately not modelled
200
+
201
+ - MQTT 3.1 and 3.1.1 (refused with that protocol's return code 1), TLS, X.509 client
202
+ certificates, ALPN and SNI, and SigV4-presigned WebSocket URLs.
203
+ - Custom authorizers over HTTP, token signing, and `disconnectAfterInSeconds` /
204
+ `refreshAfterInSeconds` (accepted, not enforced).
205
+ - Policy `Condition`, `NotAction` and `NotResource` (a document using them is refused), policy
206
+ variables other than `${iot:ClientId}`, and `iot:RetainPublish`.
207
+ - Retained messages: the CONNACK says Retain Available 0, a retained MQTT publish is answered
208
+ DISCONNECT `0x9A`, and `retain=true` over HTTP is `400`. AWS itself supports them.
209
+ - Will messages (accepted in CONNECT, never published), shared subscriptions (SUBACK `0x9E`),
210
+ keep-alive timeouts, message expiry, outbound topic aliases, and reserved `$aws/…` topics.
211
+ - Rate and count quotas (publishes per second, subscriptions per connection or per SUBSCRIBE,
212
+ the 10 messages per second replay of a stored session).
213
+ - The thing registry, shadows, rules and actions, jobs, provisioning, the other data-plane
214
+ operations, billing and infrastructure management.
215
+
216
+ ## API
217
+
218
+ | Export | Kind | Description |
219
+ | --- | --- | --- |
220
+ | `createRuntime` | function | The emulator with the full service contract. Options: `settings`, `authorizer`, `clock`, `seed`, `adminPrefix`, `adminKey`, `onLog`, `sqlite`. Adds `attach(transport, info?, namespace?)` for an MQTT connection, `webSocketPath(pathname)`, `endpoints`, and `stop()`. |
221
+ | `AwsIotAPI` | class | One namespace: `fetch(request)` for `Publish`, `broker` for MQTT, `state`, `reset()`. Options: `sqlite`, `now`, `namespace`, `settings`, `authorizer`, `connectFault`. |
222
+ | `AWS_IOT_PRESETS` | object | Every named fault preset. |
223
+ | `AWS_IOT_NAMESPACE` | string | The service name, `"aws-iot"`. |
224
+ | `SIGNING_NAME` | string | `"iotdata"`, the SigV4 signing name. |
225
+ | `MQTT_CONNECT_OPERATION`, `MQTT_WEBSOCKET_PATH` | strings | `"MqttConnect"`, the operation id a fault rule uses for a CONNECT; and `"/mqtt"`. |
226
+ | `MAX_PAYLOAD_BYTES`, `MAX_PACKET_SIZE`, `MAX_TOPIC_BYTES`, `MAX_TOPIC_SLASHES`, `MAX_CLIENT_ID_BYTES`, `MAX_MESSAGE_EXPIRY_SECONDS`, `MAX_PERSISTENT_SESSION_EXPIRY_SECONDS`, `KEEP_ALIVE_RANGE` | numbers | AWS IoT Core's limits as the emulator enforces them. |
227
+ | `DEFAULT_SETTINGS` | object | Region `us-east-1`, account `123456789012`, no credentials or authorizers, a one-hour session quota. |
228
+ | `parseSettings` | function | Validate a settings patch from JSON; throws naming the field that is wrong. |
229
+ | `parsePolicyDocument`, `isAllowed`, `resourceArn`, `PolicyError` | functions, class | Parse a policy document (object or JSON string), evaluate it for an action and target, and build the ARN an action is checked against. |
230
+ | `accessKeyCredential`, `splitUsername` | functions | The access key id of a signed request; an MQTT username and the authorizer its query string names (how credentials map to namespaces). |
231
+ | `ReasonCode` | object | MQTT 5 reason codes by name. |
232
+ | `document`, `operationIds`, `supportedOperationIds` | values | The HTTP contract and its operation ids. |
233
+ | `createServer`, `connectStream`, `loadSettingsFile`, `serveTarget`, `DEFAULT_PORT`, `DEFAULT_MQTT_PORT` (`./server`) | Node | Serve HTTP, WebSocket and TCP (`{url, mqttUrl, mqttPort, wsUrl, runtime, close()}`; options add `port`, `mqttPort`, `host`, `sweepMs`); an in-process MQTT stream; read a `--settings` file; the `serve` CLI target; ports 8851 and 8883. |
234
+
235
+ Part of [mockingbird](https://github.com/crvouga/mockingbird).
package/SUPPORT.md ADDED
@@ -0,0 +1,11 @@
1
+ # AWS IoT Core data plane (Mockingbird subset) — operation support
2
+
3
+ Generated from `openapi.yaml`; do not edit by hand.
4
+
5
+ - operations in spec: **1**
6
+ - supported by the emulator: **1**
7
+ - parity enabled: **1**
8
+
9
+ | operationId | route | emulator | parity | notes |
10
+ | --- | --- | --- | --- | --- |
11
+ | `Publish` | `POST /topics/{topic}` | ✅ supported | ⚠️ unsafe (opt-in) | |