From b0bb1e79dcc374911d4593ea8f7e06166745d8e6 Mon Sep 17 00:00:00 2001 From: Roshan Jonnalagadda <20136098+roshanjonah@users.noreply.github.com> Date: Tue, 3 Nov 2026 12:10:27 +1100 Subject: [PATCH 1/2] fix(openapi): emit explicit webhook payload discriminators --- evidence/sdk-current-contract-897-20261103.md | 11 ++ openapi-app.json | 178 ++++++++++-------- 2 files changed, 110 insertions(+), 79 deletions(-) create mode 100644 evidence/sdk-current-contract-897-20261103.md diff --git a/evidence/sdk-current-contract-897-20261103.md b/evidence/sdk-current-contract-897-20261103.md new file mode 100644 index 0000000..735c557 --- /dev/null +++ b/evidence/sdk-current-contract-897-20261103.md @@ -0,0 +1,11 @@ +# Webhook discriminator snapshot prerequisite + +Part of qubrics/qubrics#897. Companion API branch: `agent/sdk-current-contract-897`. + +The API producer names its two existing webhook payload variants so the generated snapshot carries explicit `type` discriminator mappings. Previously the inline variants had identical property names and ogen could not infer a discriminator from their enum values. This snapshot is emitted from the producer; no payload fields, event values, prose, hosts or runtime behaviour change. + +Validation: API contract regression seen red (exit 1), then green (36/36 including closed-object composition); API `pnpm check:fast` exit 0; `pnpm spec:emit --output /openapi-app.json` exit 0; `npm run postman:emit` exit 0 with no collection diff; `npm run confidentiality:check` exit 0. Normal SDK Go generation against this snapshot exits 0 without generator or current-input binding changes. Remaining generation and CI must pass before delegated merge. + +Initial Postman invocation found a missing local dependency (`ERR_MODULE_NOT_FOUND: openapi-to-postmanv2`); `npm ci` installed the committed lockfile, then the same command passed. No dependency changes. + +Logs: `/Users/rj/Desktop/GIT-REPOS/sendmux/.claude/artifacts/rebrand-qubrics-897/current-contract-{postman,confidentiality,go}.log`. Source-only merge authorised; no publish or deploy invoked. Placement/polish: generated schema metadata only, no prose or navigation change. diff --git a/openapi-app.json b/openapi-app.json index 3aeac46..8cea6e0 100644 --- a/openapi-app.json +++ b/openapi-app.json @@ -355,6 +355,43 @@ ], "type": "object" }, + "CalendarWebhookEventPayload": { + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/components/schemas/CalendarWebhookEventData" + }, + "id": { + "description": "Stable per-event identifier. Also sent in the X-Sendmux-Event-Id header.", + "example": "evt_clxxxxxxxxxxxxxxxxxxxxxxxxx", + "type": "string" + }, + "occurred_at": { + "description": "When the event occurred.", + "example": "2026-05-21T09:12:00Z", + "type": "string" + }, + "team_public_id": { + "description": "Team that owns the event.", + "example": "team_clxxxxxxxxxxxxxxxxxxxxxxxxx", + "type": "string" + }, + "type": { + "enum": [ + "calendar.changed" + ], + "type": "string" + } + }, + "required": [ + "id", + "type", + "team_public_id", + "occurred_at", + "data" + ], + "type": "object" + }, "Connection": { "additionalProperties": false, "properties": { @@ -7934,6 +7971,52 @@ ], "unevaluatedProperties": false }, + "MailWebhookEventPayload": { + "additionalProperties": false, + "properties": { + "data": { + "$ref": "#/components/schemas/WebhookEventData" + }, + "id": { + "description": "Stable per-event identifier. Also sent in the X-Sendmux-Event-Id header.", + "example": "evt_clxxxxxxxxxxxxxxxxxxxxxxxxx", + "type": "string" + }, + "occurred_at": { + "description": "When the event occurred.", + "example": "2026-05-21T09:12:00Z", + "type": "string" + }, + "team_public_id": { + "description": "Team that owns the event.", + "example": "team_clxxxxxxxxxxxxxxxxxxxxxxxxx", + "type": "string" + }, + "type": { + "description": "Dotted event type. Same value as the X-Sendmux-Event-Type header.", + "enum": [ + "message.delivered", + "message.bounced", + "message.complained", + "message.rejected", + "message.delivery_delayed", + "message.received", + "message.received.spam", + "sendmux.test" + ], + "example": "message.received", + "type": "string" + } + }, + "required": [ + "id", + "type", + "team_public_id", + "occurred_at", + "data" + ], + "type": "object" + }, "PatchCalendarEventBody": { "additionalProperties": false, "properties": { @@ -10819,6 +10902,20 @@ }, "WebhookEventPayload": { "description": "The JSON request body delivered to the webhook endpoint.", + "discriminator": { + "mapping": { + "calendar.changed": "#/components/schemas/CalendarWebhookEventPayload", + "message.bounced": "#/components/schemas/MailWebhookEventPayload", + "message.complained": "#/components/schemas/MailWebhookEventPayload", + "message.delivered": "#/components/schemas/MailWebhookEventPayload", + "message.delivery_delayed": "#/components/schemas/MailWebhookEventPayload", + "message.received": "#/components/schemas/MailWebhookEventPayload", + "message.received.spam": "#/components/schemas/MailWebhookEventPayload", + "message.rejected": "#/components/schemas/MailWebhookEventPayload", + "sendmux.test": "#/components/schemas/MailWebhookEventPayload" + }, + "propertyName": "type" + }, "example": { "data": { "bounce_subtype": null, @@ -10846,87 +10943,10 @@ }, "oneOf": [ { - "additionalProperties": false, - "properties": { - "data": { - "$ref": "#/components/schemas/WebhookEventData" - }, - "id": { - "description": "Stable per-event identifier. Also sent in the X-Sendmux-Event-Id header.", - "example": "evt_clxxxxxxxxxxxxxxxxxxxxxxxxx", - "type": "string" - }, - "occurred_at": { - "description": "When the event occurred.", - "example": "2026-05-21T09:12:00Z", - "type": "string" - }, - "team_public_id": { - "description": "Team that owns the event.", - "example": "team_clxxxxxxxxxxxxxxxxxxxxxxxxx", - "type": "string" - }, - "type": { - "description": "Dotted event type. Same value as the X-Sendmux-Event-Type header.", - "enum": [ - "message.delivered", - "message.bounced", - "message.complained", - "message.rejected", - "message.delivery_delayed", - "message.received", - "message.received.spam", - "sendmux.test" - ], - "example": "message.received", - "type": "string" - } - }, - "required": [ - "id", - "type", - "team_public_id", - "occurred_at", - "data" - ], - "type": "object" + "$ref": "#/components/schemas/MailWebhookEventPayload" }, { - "additionalProperties": false, - "properties": { - "data": { - "$ref": "#/components/schemas/CalendarWebhookEventData" - }, - "id": { - "description": "Stable per-event identifier. Also sent in the X-Sendmux-Event-Id header.", - "example": "evt_clxxxxxxxxxxxxxxxxxxxxxxxxx", - "type": "string" - }, - "occurred_at": { - "description": "When the event occurred.", - "example": "2026-05-21T09:12:00Z", - "type": "string" - }, - "team_public_id": { - "description": "Team that owns the event.", - "example": "team_clxxxxxxxxxxxxxxxxxxxxxxxxx", - "type": "string" - }, - "type": { - "enum": [ - "calendar.changed" - ], - "type": "string" - } - }, - "required": [ - "id", - "type", - "team_public_id", - "occurred_at", - "data" - ], - "type": "object" + "$ref": "#/components/schemas/CalendarWebhookEventPayload" } ] }, From 6dd1c642b4c82607e9b8578937c131a751601aff Mon Sep 17 00:00:00 2001 From: Roshan Jonnalagadda <20136098+roshanjonah@users.noreply.github.com> Date: Tue, 3 Nov 2026 13:54:47 +1100 Subject: [PATCH 2/2] fix(docs): expose calendar GET version in response snapshot --- changelog.mdx | 7 +++ evidence/sdk-current-contract-897-20261103.md | 8 ++++ openapi-app.json | 43 ++++++++++++++++++- 3 files changed, 57 insertions(+), 1 deletion(-) diff --git a/changelog.mdx b/changelog.mdx index 4ea2799..c8ed466 100644 --- a/changelog.mdx +++ b/changelog.mdx @@ -4,6 +4,13 @@ description: "New features, improvements, and fixes across the Sendmux platform. rss: true --- + + ## Calendar versions in JSON responses + +Calendar GET now returns `etag` in the JSON body, matching the `ETag` response header. CLI and MCP callers can use this value in `If-Match` to delete an empty calendar. + + + ## Delete empty mailbox calendars diff --git a/evidence/sdk-current-contract-897-20261103.md b/evidence/sdk-current-contract-897-20261103.md index 735c557..3a45fbe 100644 --- a/evidence/sdk-current-contract-897-20261103.md +++ b/evidence/sdk-current-contract-897-20261103.md @@ -9,3 +9,11 @@ Validation: API contract regression seen red (exit 1), then green (36/36 includi Initial Postman invocation found a missing local dependency (`ERR_MODULE_NOT_FOUND: openapi-to-postmanv2`); `npm ci` installed the committed lockfile, then the same command passed. No dependency changes. Logs: `/Users/rj/Desktop/GIT-REPOS/sendmux/.claude/artifacts/rebrand-qubrics-897/current-contract-{postman,confidentiality,go}.log`. Source-only merge authorised; no publish or deploy invoked. Placement/polish: generated schema metadata only, no prose or navigation change. + +## Approved Calendar GET companion + +Ops approved exposing the existing Calendar GET version as JSON `data.etag` and a closed GET-only response component. List/create retain their original schemas. The API producer matches the existing event GET pattern and adds no reads or provider calls. Runtime/schema regressions each failed before the fix, then 80 focused tests, `check:fast` and `spec:check` passed (all exit 0). + +Placement: existing generated Mailbox API GET reference and current-day changelog entry. Disposition: keep. Fresh-reader critique corrected the missing empty-calendar qualifier; humanize ran once and protected `etag`, `ETag`, `If-Match`, Calendar GET, CLI and MCP plus body/header equivalence. Final copy passed every applicable rubric line; no navigation changes. Generated snapshot uses the producer output; no hand edits or Postman bypass. + +This is a source prerequisite, not publication or deployment proof. Normal SDK generation and exact-head CI remain merge gates. diff --git a/openapi-app.json b/openapi-app.json index 8cea6e0..60c42b1 100644 --- a/openapi-app.json +++ b/openapi-app.json @@ -3251,7 +3251,7 @@ { "properties": { "data": { - "$ref": "#/components/schemas/MailboxCalendar" + "$ref": "#/components/schemas/MailboxCalendarWithEtag" }, "meta": { "$ref": "#/components/schemas/ResponseMeta" @@ -3265,6 +3265,47 @@ ], "unevaluatedProperties": false }, + "MailboxCalendarWithEtag": { + "additionalProperties": false, + "properties": { + "etag": { + "description": "Current calendar version, matching the ETag response header. Use this value in If-Match to delete an empty calendar.", + "type": "string" + }, + "id": { + "description": "Opaque identifier scoped to the selected mailbox.", + "maxLength": 4096, + "minLength": 1, + "pattern": "^[A-Za-z0-9_-]+$", + "type": "string" + }, + "is_default": { + "description": "Whether this is the mailbox's current default calendar.", + "type": "boolean" + }, + "mailbox_id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "time_zone": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "id", + "mailbox_id", + "name", + "time_zone", + "is_default", + "etag" + ], + "type": "object" + }, "MailboxChanges": { "additionalProperties": false, "properties": {