@fourier-labs/harbour-contracts 0.72.0 → 0.74.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.
Files changed (31) hide show
  1. package/README.md +2 -2
  2. package/contract-release.json +24 -24
  3. package/dist/gen-ts/app-integrations-declaration.d.ts +29 -20
  4. package/dist/gen-ts/contract-release.d.ts +6 -6
  5. package/dist/gen-ts/contract-release.js +6 -6
  6. package/dist/gen-ts/data-grant-v2.d.ts +2 -2
  7. package/dist/gen-ts/data-source.d.ts +19 -2
  8. package/dist/gen-ts/integration-error-details.d.ts +2 -2
  9. package/dist/gen-ts/integration-execute-input-rules.d.ts +35 -0
  10. package/dist/gen-ts/integration-execute-input-rules.js +15 -0
  11. package/dist/gen-ts/integration-execute-request.d.ts +24 -2
  12. package/dist/gen-ts/integration-execute-result.d.ts +9 -0
  13. package/dist/gen-ts/integration-operations.d.ts +7 -0
  14. package/dist/gen-ts/integration-operations.js +9 -0
  15. package/dist/gen-ts/saas-connection.d.ts +1 -1
  16. package/gen-ts/app-integrations-declaration.ts +29 -20
  17. package/gen-ts/contract-release.ts +6 -6
  18. package/gen-ts/data-grant-v2.ts +2 -1
  19. package/gen-ts/data-source.ts +19 -2
  20. package/gen-ts/integration-error-details.ts +3 -2
  21. package/gen-ts/integration-execute-input-rules.ts +15 -0
  22. package/gen-ts/integration-execute-request.ts +25 -2
  23. package/gen-ts/integration-execute-result.ts +9 -0
  24. package/gen-ts/integration-operations.ts +9 -0
  25. package/gen-ts/saas-connection.ts +1 -0
  26. package/package.json +1 -1
  27. package/schema/app-integrations-declaration.schema.json +478 -67
  28. package/schema/data-source.schema.json +57 -2
  29. package/schema/integration-error-details.schema.json +3 -2
  30. package/schema/integration-execute-request.schema.json +905 -120
  31. package/schema/integration-execute-result.schema.json +520 -82
@@ -2,79 +2,237 @@
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
3
  "$id": "https://schemas.harbour.dev/integration-execute-request.schema.json",
4
4
  "title": "IntegrationExecuteRequest",
5
- "description": "Body of POST /_harbour/integrations/execute (app SDK), forwarded unchanged to the governance development and runtime execute routes. Exactly one declared operation against one declared resource. The identity mode is fixed by the operation for every operation but the declared-mode Slack operations (slack.channel.history, slack.message.post, slack.message.update, slack.message.delete; 0.71.0 widened history), and always enforced from an approved grant; any identity/userId/scope field is rejected by additionalProperties. A declared-mode operation runs as the app (for a post: the IT-approved per-app name from the grant, plus a \"Posted by <app> on Isomorph\" footer) or as the signed-in person (no name, no footer) — the same input either way — and since 0.55.0 the call names which of the app's approved modes it runs under (`mode`): required when IT approved both modes for the app (MODE_REQUIRED otherwise), optional when it approved one (that one is used), and a named mode without an approved grant is refused (MODE_NOT_GRANTED) rather than served under the other, so approval order never decides what a call does. Per-operation input bounds (0.71.0, ADR 0018): slack.channel.history {limit? 1..100, default 15, cursor?, oldest?, latest?}; slack.message.post {text 1..4000 chars, threadId?, format? plain|mrkdwn} and a required idempotencyKey retained across retries of one explicit Send; slack.message.update {messageId, text, format?}; slack.message.delete {messageId}; slack.dm.send {to 1..8 addresses, text, format?} (app mode only) with a required idempotencyKey; slack.user.lookup {email}; gmail.thread.list {limit? 1..50, default 15, q?, labelIds? default [INBOX], pageToken?}; gmail.thread.read {threadId}; gmail.message.read {messageId}; gmail.attachment.read {messageId, attachmentId}; gmail.message.send {to 1..50 addresses (to+cc+bcc <= 50 in total), cc?, bcc?, subject 1..256 chars, text 1..4000 chars, html? <= 64 KiB, attachments? 1..5 of <= 2 MiB each as base64, replyToMessageId?} and a required idempotencyKey like slack.message.post; warehouse.view.read {columns? 1..32, where? 1..16 filters, groupBy? 1..8 columns, aggregates? 1..16, orderBy? 1..8, limit? 1..1000, default 100} (0.70.0: a plain read names columns, an aggregate read names aggregates and optionally groupBy; the cross-field rules — exactly one of columns or aggregates, groupBy needs aggregates, a filter value that fits its op, orderBy naming a returned column, every named column approved — are enforced by governance, not expressible here; ADR 0017). Each operation's then-block is the one statement of its input rules: propertyNames.enum names the fields it accepts, required the ones it needs, and per-field overrides (maximum, default) narrow the shared property. Codegen derives IntegrationExecuteInputRules and validateIntegrationExecuteInput (gen-ts/integration-execute-input-rules.ts, gen-go/integration_execute_input_rules.go) from exactly these blocks, so the governance execute route, the app SDK and the kit's local check refuse the same request with the same message. Transport limits (not expressible here): request <= 16 KiB except a gmail.message.send carrying attachments (governance sizes that route for the attachment bound), total deadline 10 s.",
5
+ "description": "Body of POST /_harbour/integrations/execute (app SDK), forwarded unchanged to the governance development and runtime execute routes. Exactly one declared operation against one declared resource. The identity mode is fixed by the operation for every operation but the declared-mode Slack operations (slack.channel.history, slack.message.post, slack.message.update, slack.message.delete; 0.71.0 widened history), and always enforced from an approved grant; any identity/userId/scope field is rejected by additionalProperties. A declared-mode operation runs as the app (for a post: the IT-approved per-app name from the grant, plus a \"Posted by <app> on Isomorph\" footer) or as the signed-in person (no name, no footer) \u2014 the same input either way \u2014 and since 0.55.0 the call names which of the app's approved modes it runs under (`mode`): required when IT approved both modes for the app (MODE_REQUIRED otherwise), optional when it approved one (that one is used), and a named mode without an approved grant is refused (MODE_NOT_GRANTED) rather than served under the other, so approval order never decides what a call does. Per-operation input bounds (0.71.0, ADR 0018): slack.channel.history {limit? 1..100, default 15, cursor?, oldest?, latest?}; slack.message.post {text 1..4000 chars, threadId?, format? plain|mrkdwn} and a required idempotencyKey retained across retries of one explicit Send; slack.message.update {messageId, text, format?}; slack.message.delete {messageId}; slack.dm.send {to 1..8 addresses, text, format?} (app mode only) with a required idempotencyKey; slack.user.lookup {email}; gmail.thread.list {limit? 1..50, default 15, q?, labelIds? default [INBOX], pageToken?}; gmail.thread.read {threadId}; gmail.message.read {messageId}; gmail.attachment.read {messageId, attachmentId}; gmail.message.send {to 1..50 addresses (to+cc+bcc <= 50 in total), cc?, bcc?, subject 1..256 chars, text 1..4000 chars, html? <= 64 KiB, attachments? 1..5 of <= 2 MiB each as base64, replyToMessageId?} and a required idempotencyKey like slack.message.post; warehouse.view.read {columns? 1..32, where? 1..16 filters, groupBy? 1..8 columns, aggregates? 1..16, orderBy? 1..8, limit? 1..1000, default 100} (0.70.0: a plain read names columns, an aggregate read names aggregates and optionally groupBy; the cross-field rules \u2014 exactly one of columns or aggregates, groupBy needs aggregates, a filter value that fits its op, orderBy naming a returned column, every named column approved \u2014 are enforced by governance, not expressible here; ADR 0017). Each operation's then-block is the one statement of its input rules: propertyNames.enum names the fields it accepts, required the ones it needs, and per-field overrides (maximum, default) narrow the shared property. Codegen derives IntegrationExecuteInputRules and validateIntegrationExecuteInput (gen-ts/integration-execute-input-rules.ts, gen-go/integration_execute_input_rules.go) from exactly these blocks, so the governance execute route, the app SDK and the kit's local check refuse the same request with the same message. Transport limits (not expressible here): request <= 16 KiB except a gmail.message.send carrying attachments (governance sizes that route for the attachment bound), total deadline 10 s.",
6
6
  "type": "object",
7
7
  "additionalProperties": false,
8
- "required": ["connection", "operation", "resource", "input"],
8
+ "required": [
9
+ "connection",
10
+ "operation",
11
+ "input"
12
+ ],
9
13
  "properties": {
10
- "connection": { "$ref": "app-integrations-declaration.schema.json#/definitions/IntegrationAlias" },
11
- "operation": { "$ref": "app-integrations-declaration.schema.json#/definitions/IntegrationOperation" },
12
- "resource": { "$ref": "app-integrations-declaration.schema.json#/definitions/IntegrationAlias" },
14
+ "connection": {
15
+ "$ref": "app-integrations-declaration.schema.json#/definitions/IntegrationAlias"
16
+ },
17
+ "operation": {
18
+ "$ref": "app-integrations-declaration.schema.json#/definitions/IntegrationOperation"
19
+ },
20
+ "resource": {
21
+ "$ref": "app-integrations-declaration.schema.json#/definitions/IntegrationAlias"
22
+ },
13
23
  "input": {
14
24
  "type": "object",
15
25
  "additionalProperties": false,
16
26
  "properties": {
17
- "limit": { "type": "integer", "minimum": 1, "maximum": 1000, "description": "Read operations: how many items to return; the per-operation then-block sets the real ceiling (100 for slack.channel.history, 50 for gmail.thread.list, 1000 for warehouse.view.read) and the default." },
18
- "text": { "type": "string", "minLength": 1, "maxLength": 4000, "description": "slack.message.post / slack.message.update / slack.dm.send / gmail.message.send: the body, 1..4000 characters (plain text, or Slack mrkdwn when `format` says so)." },
19
- "format": { "$ref": "#/definitions/SlackMessageFormat", "description": "slack.message.post / slack.message.update / slack.dm.send (0.71.0): how Slack renders `text`; plain when absent." },
20
- "threadId": { "type": "string", "pattern": "^[A-Za-z0-9._-]{1,64}$", "description": "slack.message.post (0.71.0): the Slack message ts (a slack.channel.history message id) the post replies to, so it lands in that thread. gmail.thread.read: the Gmail thread id a gmail.thread.list result named." },
21
- "messageId": { "type": "string", "pattern": "^[A-Za-z0-9._-]{1,64}$", "description": "gmail.message.read / gmail.attachment.read: the Gmail message id a gmail.thread.list or gmail.thread.read result named. slack.message.update / slack.message.delete (0.71.0): the Slack message ts a post or history result named." },
22
- "attachmentId": { "type": "string", "minLength": 1, "maxLength": 512, "description": "gmail.attachment.read (0.71.0): the attachment id a gmail.message.read or gmail.thread.read result named." },
23
- "cursor": { "type": "string", "minLength": 1, "maxLength": 256, "description": "slack.channel.history (0.71.0): the `nextCursor` of the previous page; absent reads the newest page." },
24
- "oldest": { "type": "string", "minLength": 1, "maxLength": 64, "description": "slack.channel.history (0.71.0): only messages after this Slack ts." },
25
- "latest": { "type": "string", "minLength": 1, "maxLength": 64, "description": "slack.channel.history (0.71.0): only messages before this Slack ts." },
26
- "q": { "type": "string", "minLength": 1, "maxLength": 256, "description": "gmail.thread.list (0.71.0): a Gmail search query (the search-box syntax, e.g. `from:jane newer_than:7d`)." },
27
- "labelIds": { "type": "array", "minItems": 1, "maxItems": 5, "uniqueItems": true, "items": { "type": "string", "minLength": 1, "maxLength": 64 }, "default": ["INBOX"], "description": "gmail.thread.list (0.71.0): the Gmail labels a thread must carry (all of them); INBOX when absent." },
28
- "pageToken": { "type": "string", "minLength": 1, "maxLength": 256, "description": "gmail.thread.list (0.71.0): the `nextPageToken` of the previous page; absent reads the first page." },
29
- "email": { "$ref": "#/definitions/EmailAddress", "description": "slack.user.lookup (0.71.0): the workspace member's email address to resolve to a Slack user id." },
30
- "to": { "$ref": "#/definitions/EmailAddressList", "description": "gmail.message.send: primary recipients. slack.dm.send (0.71.0): the workspace members to message, by email, 1..8; several open one group DM." },
31
- "cc": { "$ref": "#/definitions/EmailAddressList", "description": "gmail.message.send: carbon-copy recipients." },
32
- "bcc": { "$ref": "#/definitions/EmailAddressList", "description": "gmail.message.send: blind-copy recipients." },
33
- "subject": { "type": "string", "minLength": 1, "maxLength": 256, "description": "gmail.message.send: one-line subject (no line breaks)." },
34
- "replyToMessageId": { "type": "string", "pattern": "^[A-Za-z0-9_-]{1,64}$", "description": "gmail.message.send: a Gmail message id (from gmail.thread.list / gmail.message.read) the email replies to; the send joins that thread." },
35
- "html": { "type": "string", "minLength": 1, "maxLength": 65536, "description": "gmail.message.send (0.71.0): an HTML body sent beside `text` (which stays the plain-text alternative), at most 64 KiB." },
27
+ "limit": {
28
+ "type": "integer",
29
+ "minimum": 1,
30
+ "maximum": 1000,
31
+ "description": "Read operations: how many items to return; the per-operation then-block sets the real ceiling (100 for slack.channel.history, 50 for gmail.thread.list, 1000 for warehouse.view.read) and the default."
32
+ },
33
+ "text": {
34
+ "type": "string",
35
+ "minLength": 1,
36
+ "maxLength": 4000,
37
+ "description": "slack.message.post / slack.message.update / slack.dm.send / gmail.message.send: the body, 1..4000 characters (plain text, or Slack mrkdwn when `format` says so)."
38
+ },
39
+ "format": {
40
+ "$ref": "#/definitions/SlackMessageFormat",
41
+ "description": "slack.message.post / slack.message.update / slack.dm.send (0.71.0): how Slack renders `text`; plain when absent."
42
+ },
43
+ "threadId": {
44
+ "type": "string",
45
+ "pattern": "^[A-Za-z0-9._-]{1,64}$",
46
+ "description": "slack.message.post (0.71.0): the Slack message ts (a slack.channel.history message id) the post replies to, so it lands in that thread. gmail.thread.read: the Gmail thread id a gmail.thread.list result named."
47
+ },
48
+ "messageId": {
49
+ "type": "string",
50
+ "pattern": "^[A-Za-z0-9._-]{1,64}$",
51
+ "description": "gmail.message.read / gmail.attachment.read: the Gmail message id a gmail.thread.list or gmail.thread.read result named. slack.message.update / slack.message.delete (0.71.0): the Slack message ts a post or history result named."
52
+ },
53
+ "attachmentId": {
54
+ "type": "string",
55
+ "minLength": 1,
56
+ "maxLength": 512,
57
+ "description": "gmail.attachment.read (0.71.0): the attachment id a gmail.message.read or gmail.thread.read result named."
58
+ },
59
+ "cursor": {
60
+ "type": "string",
61
+ "minLength": 1,
62
+ "maxLength": 256,
63
+ "description": "slack.channel.history (0.71.0): the `nextCursor` of the previous page; absent reads the newest page."
64
+ },
65
+ "oldest": {
66
+ "type": "string",
67
+ "minLength": 1,
68
+ "maxLength": 64,
69
+ "description": "slack.channel.history (0.71.0): only messages after this Slack ts."
70
+ },
71
+ "latest": {
72
+ "type": "string",
73
+ "minLength": 1,
74
+ "maxLength": 64,
75
+ "description": "slack.channel.history (0.71.0): only messages before this Slack ts."
76
+ },
77
+ "q": {
78
+ "type": "string",
79
+ "minLength": 1,
80
+ "maxLength": 256,
81
+ "description": "gmail.thread.list (0.71.0): a Gmail search query (the search-box syntax, e.g. `from:jane newer_than:7d`)."
82
+ },
83
+ "labelIds": {
84
+ "type": "array",
85
+ "minItems": 1,
86
+ "maxItems": 5,
87
+ "uniqueItems": true,
88
+ "items": {
89
+ "type": "string",
90
+ "minLength": 1,
91
+ "maxLength": 64
92
+ },
93
+ "default": [
94
+ "INBOX"
95
+ ],
96
+ "description": "gmail.thread.list (0.71.0): the Gmail labels a thread must carry (all of them); INBOX when absent."
97
+ },
98
+ "pageToken": {
99
+ "type": "string",
100
+ "minLength": 1,
101
+ "maxLength": 256,
102
+ "description": "gmail.thread.list (0.71.0): the `nextPageToken` of the previous page; absent reads the first page."
103
+ },
104
+ "email": {
105
+ "$ref": "#/definitions/EmailAddress",
106
+ "description": "slack.user.lookup (0.71.0): the workspace member's email address to resolve to a Slack user id."
107
+ },
108
+ "to": {
109
+ "$ref": "#/definitions/EmailAddressList",
110
+ "description": "gmail.message.send: primary recipients. slack.dm.send (0.71.0): the workspace members to message, by email, 1..8; several open one group DM."
111
+ },
112
+ "cc": {
113
+ "$ref": "#/definitions/EmailAddressList",
114
+ "description": "gmail.message.send: carbon-copy recipients."
115
+ },
116
+ "bcc": {
117
+ "$ref": "#/definitions/EmailAddressList",
118
+ "description": "gmail.message.send: blind-copy recipients."
119
+ },
120
+ "subject": {
121
+ "type": "string",
122
+ "minLength": 1,
123
+ "maxLength": 256,
124
+ "description": "gmail.message.send: one-line subject (no line breaks)."
125
+ },
126
+ "replyToMessageId": {
127
+ "type": "string",
128
+ "pattern": "^[A-Za-z0-9_-]{1,64}$",
129
+ "description": "gmail.message.send: a Gmail message id (from gmail.thread.list / gmail.message.read) the email replies to; the send joins that thread."
130
+ },
131
+ "html": {
132
+ "type": "string",
133
+ "minLength": 1,
134
+ "maxLength": 65536,
135
+ "description": "gmail.message.send (0.71.0): an HTML body sent beside `text` (which stays the plain-text alternative), at most 64 KiB."
136
+ },
36
137
  "attachments": {
37
138
  "type": "array",
38
139
  "minItems": 1,
39
140
  "maxItems": 5,
40
- "items": { "$ref": "#/definitions/EmailAttachment" },
141
+ "items": {
142
+ "$ref": "#/definitions/EmailAttachment"
143
+ },
41
144
  "description": "gmail.message.send (0.71.0): files to attach, 1..5, each at most 2 MiB (2,796,203 base64 characters)."
42
145
  },
43
146
  "columns": {
44
147
  "type": "array",
45
148
  "minItems": 1,
46
149
  "maxItems": 32,
47
- "items": { "$ref": "app-integrations-declaration.schema.json#/definitions/ColumnName" },
48
- "not": { "allOf": [{ "contains": { "const": "*" } }, { "minItems": 2 }] },
150
+ "items": {
151
+ "$ref": "app-integrations-declaration.schema.json#/definitions/ColumnName"
152
+ },
153
+ "not": {
154
+ "allOf": [
155
+ {
156
+ "contains": {
157
+ "const": "*"
158
+ }
159
+ },
160
+ {
161
+ "minItems": 2
162
+ }
163
+ ]
164
+ },
49
165
  "description": "warehouse.view.read, a plain read: explicit columns, or `*` alone when the app's approved resource grant allows all current and future columns. An aggregate read names aggregates instead."
50
166
  },
51
167
  "where": {
52
168
  "type": "array",
53
169
  "minItems": 1,
54
170
  "maxItems": 16,
55
- "items": { "$ref": "#/definitions/WarehouseFilter" },
171
+ "items": {
172
+ "$ref": "#/definitions/WarehouseFilter"
173
+ },
56
174
  "description": "warehouse.view.read (0.70.0): filters the warehouse applies before it returns, grouping or limits rows. All of them must hold (AND); there is no OR and no LIKE."
57
175
  },
58
176
  "groupBy": {
59
177
  "type": "array",
60
178
  "minItems": 1,
61
179
  "maxItems": 8,
62
- "items": { "type": "string", "pattern": "^[A-Za-z_][A-Za-z0-9_]{0,62}$" },
180
+ "items": {
181
+ "type": "string",
182
+ "pattern": "^[A-Za-z_][A-Za-z0-9_]{0,62}$"
183
+ },
63
184
  "description": "warehouse.view.read (0.70.0): columns (never `*`) that group the rows; needs aggregates. Each returned row is one group: its groupBy columns, then its aggregate aliases."
64
185
  },
65
186
  "aggregates": {
66
187
  "type": "array",
67
188
  "minItems": 1,
68
189
  "maxItems": 16,
69
- "items": { "$ref": "#/definitions/WarehouseAggregate" },
190
+ "items": {
191
+ "$ref": "#/definitions/WarehouseAggregate"
192
+ },
70
193
  "description": "warehouse.view.read (0.70.0): what an aggregate read computes, over each group or over every matching row when there is no groupBy."
71
194
  },
72
195
  "orderBy": {
73
196
  "type": "array",
74
197
  "minItems": 1,
75
198
  "maxItems": 8,
76
- "items": { "$ref": "#/definitions/WarehouseOrder" },
199
+ "items": {
200
+ "$ref": "#/definitions/WarehouseOrder"
201
+ },
77
202
  "description": "warehouse.view.read (0.70.0): sort keys, first key first. Without orderBy an aggregate read is sorted by its groupBy columns and a plain read is not sorted at all."
203
+ },
204
+ "method": {
205
+ "type": "string",
206
+ "enum": [
207
+ "GET",
208
+ "POST",
209
+ "PUT",
210
+ "PATCH",
211
+ "DELETE",
212
+ "HEAD",
213
+ "OPTIONS"
214
+ ]
215
+ },
216
+ "path": {
217
+ "type": "string",
218
+ "minLength": 0,
219
+ "maxLength": 16384
220
+ },
221
+ "pathParams": {
222
+ "type": "object",
223
+ "additionalProperties": true
224
+ },
225
+ "query": {
226
+ "type": "object",
227
+ "additionalProperties": true
228
+ },
229
+ "headers": {
230
+ "type": "object",
231
+ "additionalProperties": true
232
+ },
233
+ "body": {
234
+ "type": "object",
235
+ "additionalProperties": true
78
236
  }
79
237
  }
80
238
  },
@@ -85,241 +243,711 @@
85
243
  },
86
244
  "mode": {
87
245
  "$ref": "app-integrations-declaration.schema.json#/definitions/IntegrationIdentityMode",
88
- "description": "The declared-mode Slack operations only (slack.message.post since 0.55.0; slack.channel.history, slack.message.update and slack.message.delete since 0.71.0): which of the app's IT-approved modes this call runs under — app (the company's Slack app; a post goes out under the grant's per-app name and the footer) or user (the signed-in person, after their consent; no name, no footer). Required when the app holds approved grants for both modes (else MODE_REQUIRED); may be omitted when it holds one, which is then used. A named mode without an approved grant is refused with MODE_NOT_GRANTED; governance never falls back to the other mode. Every other operation's mode is fixed, so the field is refused there."
246
+ "description": "The declared-mode Slack operations only (slack.message.post since 0.55.0; slack.channel.history, slack.message.update and slack.message.delete since 0.71.0): which of the app's IT-approved modes this call runs under \u2014 app (the company's Slack app; a post goes out under the grant's per-app name and the footer) or user (the signed-in person, after their consent; no name, no footer). Required when the app holds approved grants for both modes (else MODE_REQUIRED); may be omitted when it holds one, which is then used. A named mode without an approved grant is refused with MODE_NOT_GRANTED; governance never falls back to the other mode. Every other operation's mode is fixed, so the field is refused there."
247
+ },
248
+ "sampleRows": {
249
+ "type": "array",
250
+ "maxItems": 50,
251
+ "items": {
252
+ "type": "object",
253
+ "additionalProperties": {
254
+ "type": ["string", "number", "boolean", "null"]
255
+ },
256
+ "description": "One sample row keyed by column name; each value is a scalar that governance parses by the column's recorded type."
257
+ },
258
+ "description": "Development only (0.74.0). Rows the kit dev runtime attaches for the resource being read, from the sample-row file the agent wrote into the app's .isomorph/ folder. Governance serves them for a development warehouse.view.read when the source has no development copy (data-source.environments.development): it parses each value by the resource's recorded column type and refuses a value that does not parse, naming the column and the row. The field is refused with SAMPLE_ROWS_NOT_ALLOWED on any route but the development one, so a preview or production read never carries made-up data. Not part of the per-operation input rules; the dev runtime adds it beside the app's request."
89
259
  }
90
260
  },
91
261
  "allOf": [
92
262
  {
93
- "if": { "properties": { "operation": { "const": "slack.channel.history" } } },
263
+ "if": {
264
+ "properties": {
265
+ "operation": {
266
+ "const": "slack.channel.history"
267
+ }
268
+ }
269
+ },
94
270
  "then": {
95
271
  "x-read-only": true,
96
272
  "x-primary-identity": "user",
97
- "not": { "required": ["idempotencyKey"] },
273
+ "not": {
274
+ "required": [
275
+ "idempotencyKey"
276
+ ]
277
+ },
98
278
  "properties": {
99
- "input": { "propertyNames": { "enum": ["limit", "cursor", "oldest", "latest"] }, "properties": { "limit": { "maximum": 100, "default": 15 } } }
279
+ "input": {
280
+ "propertyNames": {
281
+ "enum": [
282
+ "limit",
283
+ "cursor",
284
+ "oldest",
285
+ "latest"
286
+ ]
287
+ },
288
+ "properties": {
289
+ "limit": {
290
+ "maximum": 100,
291
+ "default": 15
292
+ }
293
+ }
294
+ }
100
295
  }
101
296
  }
102
297
  },
103
298
  {
104
- "if": { "properties": { "operation": { "const": "slack.message.post" } } },
299
+ "if": {
300
+ "properties": {
301
+ "operation": {
302
+ "const": "slack.message.post"
303
+ }
304
+ }
305
+ },
105
306
  "then": {
106
307
  "x-read-only": false,
107
- "required": ["idempotencyKey"],
308
+ "required": [
309
+ "idempotencyKey"
310
+ ],
108
311
  "properties": {
109
- "input": { "propertyNames": { "enum": ["text", "threadId", "format"] }, "required": ["text"] }
312
+ "input": {
313
+ "propertyNames": {
314
+ "enum": [
315
+ "text",
316
+ "threadId",
317
+ "format"
318
+ ]
319
+ },
320
+ "required": [
321
+ "text"
322
+ ]
323
+ }
110
324
  }
111
325
  }
112
326
  },
113
327
  {
114
- "if": { "properties": { "operation": { "const": "slack.message.update" } } },
328
+ "if": {
329
+ "properties": {
330
+ "operation": {
331
+ "const": "slack.message.update"
332
+ }
333
+ }
334
+ },
115
335
  "then": {
116
336
  "x-read-only": false,
117
- "not": { "required": ["idempotencyKey"] },
337
+ "not": {
338
+ "required": [
339
+ "idempotencyKey"
340
+ ]
341
+ },
118
342
  "properties": {
119
- "input": { "propertyNames": { "enum": ["messageId", "text", "format"] }, "required": ["messageId", "text"] }
343
+ "input": {
344
+ "propertyNames": {
345
+ "enum": [
346
+ "messageId",
347
+ "text",
348
+ "format"
349
+ ]
350
+ },
351
+ "required": [
352
+ "messageId",
353
+ "text"
354
+ ]
355
+ }
120
356
  }
121
357
  }
122
358
  },
123
359
  {
124
- "if": { "properties": { "operation": { "const": "slack.message.delete" } } },
360
+ "if": {
361
+ "properties": {
362
+ "operation": {
363
+ "const": "slack.message.delete"
364
+ }
365
+ }
366
+ },
125
367
  "then": {
126
368
  "x-read-only": false,
127
- "not": { "required": ["idempotencyKey"] },
369
+ "not": {
370
+ "required": [
371
+ "idempotencyKey"
372
+ ]
373
+ },
128
374
  "properties": {
129
- "input": { "propertyNames": { "enum": ["messageId"] }, "required": ["messageId"] }
375
+ "input": {
376
+ "propertyNames": {
377
+ "enum": [
378
+ "messageId"
379
+ ]
380
+ },
381
+ "required": [
382
+ "messageId"
383
+ ]
384
+ }
130
385
  }
131
386
  }
132
387
  },
133
388
  {
134
- "if": { "properties": { "operation": { "const": "slack.dm.send" } } },
389
+ "if": {
390
+ "properties": {
391
+ "operation": {
392
+ "const": "slack.dm.send"
393
+ }
394
+ }
395
+ },
135
396
  "then": {
136
397
  "x-read-only": false,
137
- "required": ["idempotencyKey"],
398
+ "required": [
399
+ "idempotencyKey"
400
+ ],
138
401
  "properties": {
139
402
  "mode": false,
140
- "input": { "propertyNames": { "enum": ["to", "text", "format"] }, "required": ["to", "text"], "properties": { "to": { "maxItems": 8 } } }
403
+ "input": {
404
+ "propertyNames": {
405
+ "enum": [
406
+ "to",
407
+ "text",
408
+ "format"
409
+ ]
410
+ },
411
+ "required": [
412
+ "to",
413
+ "text"
414
+ ],
415
+ "properties": {
416
+ "to": {
417
+ "maxItems": 8
418
+ }
419
+ }
420
+ }
141
421
  }
142
422
  }
143
423
  },
144
424
  {
145
- "if": { "properties": { "operation": { "const": "slack.user.lookup" } } },
425
+ "if": {
426
+ "properties": {
427
+ "operation": {
428
+ "const": "slack.user.lookup"
429
+ }
430
+ }
431
+ },
146
432
  "then": {
147
433
  "x-read-only": true,
148
- "not": { "required": ["idempotencyKey"] },
434
+ "not": {
435
+ "required": [
436
+ "idempotencyKey"
437
+ ]
438
+ },
149
439
  "properties": {
150
440
  "mode": false,
151
- "input": { "propertyNames": { "enum": ["email"] }, "required": ["email"] }
441
+ "input": {
442
+ "propertyNames": {
443
+ "enum": [
444
+ "email"
445
+ ]
446
+ },
447
+ "required": [
448
+ "email"
449
+ ]
450
+ }
152
451
  }
153
452
  }
154
453
  },
155
454
  {
156
- "if": { "properties": { "operation": { "const": "warehouse.view.read" } } },
455
+ "if": {
456
+ "properties": {
457
+ "operation": {
458
+ "const": "warehouse.view.read"
459
+ }
460
+ }
461
+ },
157
462
  "then": {
158
463
  "x-read-only": true,
159
- "not": { "required": ["idempotencyKey"] },
464
+ "not": {
465
+ "required": [
466
+ "idempotencyKey"
467
+ ]
468
+ },
160
469
  "properties": {
161
470
  "mode": false,
162
- "input": { "propertyNames": { "enum": ["columns", "where", "groupBy", "aggregates", "orderBy", "limit"] }, "properties": { "limit": { "maximum": 1000, "default": 100 } } }
471
+ "input": {
472
+ "propertyNames": {
473
+ "enum": [
474
+ "columns",
475
+ "where",
476
+ "groupBy",
477
+ "aggregates",
478
+ "orderBy",
479
+ "limit"
480
+ ]
481
+ },
482
+ "properties": {
483
+ "limit": {
484
+ "maximum": 1000,
485
+ "default": 100
486
+ }
487
+ }
488
+ }
163
489
  }
164
490
  }
165
491
  },
166
492
  {
167
- "if": { "properties": { "operation": { "const": "gmail.thread.list" } } },
493
+ "if": {
494
+ "properties": {
495
+ "operation": {
496
+ "const": "gmail.thread.list"
497
+ }
498
+ }
499
+ },
168
500
  "then": {
169
501
  "x-read-only": true,
170
- "not": { "required": ["idempotencyKey"] },
502
+ "not": {
503
+ "required": [
504
+ "idempotencyKey"
505
+ ]
506
+ },
171
507
  "properties": {
172
508
  "mode": false,
173
- "input": { "propertyNames": { "enum": ["limit", "q", "labelIds", "pageToken"] }, "properties": { "limit": { "maximum": 50, "default": 15 } } }
509
+ "input": {
510
+ "propertyNames": {
511
+ "enum": [
512
+ "limit",
513
+ "q",
514
+ "labelIds",
515
+ "pageToken"
516
+ ]
517
+ },
518
+ "properties": {
519
+ "limit": {
520
+ "maximum": 50,
521
+ "default": 15
522
+ }
523
+ }
524
+ }
174
525
  }
175
526
  }
176
527
  },
177
528
  {
178
- "if": { "properties": { "operation": { "const": "gmail.thread.read" } } },
529
+ "if": {
530
+ "properties": {
531
+ "operation": {
532
+ "const": "gmail.thread.read"
533
+ }
534
+ }
535
+ },
179
536
  "then": {
180
537
  "x-read-only": true,
181
- "not": { "required": ["idempotencyKey"] },
538
+ "not": {
539
+ "required": [
540
+ "idempotencyKey"
541
+ ]
542
+ },
182
543
  "properties": {
183
544
  "mode": false,
184
- "input": { "propertyNames": { "enum": ["threadId"] }, "required": ["threadId"] }
545
+ "input": {
546
+ "propertyNames": {
547
+ "enum": [
548
+ "threadId"
549
+ ]
550
+ },
551
+ "required": [
552
+ "threadId"
553
+ ]
554
+ }
185
555
  }
186
556
  }
187
557
  },
188
558
  {
189
- "if": { "properties": { "operation": { "const": "gmail.message.read" } } },
559
+ "if": {
560
+ "properties": {
561
+ "operation": {
562
+ "const": "gmail.message.read"
563
+ }
564
+ }
565
+ },
190
566
  "then": {
191
567
  "x-read-only": true,
192
- "not": { "required": ["idempotencyKey"] },
568
+ "not": {
569
+ "required": [
570
+ "idempotencyKey"
571
+ ]
572
+ },
193
573
  "properties": {
194
574
  "mode": false,
195
- "input": { "propertyNames": { "enum": ["messageId"] }, "required": ["messageId"] }
575
+ "input": {
576
+ "propertyNames": {
577
+ "enum": [
578
+ "messageId"
579
+ ]
580
+ },
581
+ "required": [
582
+ "messageId"
583
+ ]
584
+ }
196
585
  }
197
586
  }
198
587
  },
199
588
  {
200
- "if": { "properties": { "operation": { "const": "gmail.attachment.read" } } },
589
+ "if": {
590
+ "properties": {
591
+ "operation": {
592
+ "const": "gmail.attachment.read"
593
+ }
594
+ }
595
+ },
201
596
  "then": {
202
597
  "x-read-only": true,
203
- "not": { "required": ["idempotencyKey"] },
598
+ "not": {
599
+ "required": [
600
+ "idempotencyKey"
601
+ ]
602
+ },
603
+ "properties": {
604
+ "mode": false,
605
+ "input": {
606
+ "propertyNames": {
607
+ "enum": [
608
+ "messageId",
609
+ "attachmentId"
610
+ ]
611
+ },
612
+ "required": [
613
+ "messageId",
614
+ "attachmentId"
615
+ ]
616
+ }
617
+ }
618
+ }
619
+ },
620
+ {
621
+ "if": {
622
+ "properties": {
623
+ "operation": {
624
+ "const": "gmail.message.send"
625
+ }
626
+ }
627
+ },
628
+ "then": {
629
+ "x-read-only": false,
630
+ "required": [
631
+ "idempotencyKey"
632
+ ],
204
633
  "properties": {
205
634
  "mode": false,
206
- "input": { "propertyNames": { "enum": ["messageId", "attachmentId"] }, "required": ["messageId", "attachmentId"] }
635
+ "input": {
636
+ "propertyNames": {
637
+ "enum": [
638
+ "to",
639
+ "cc",
640
+ "bcc",
641
+ "subject",
642
+ "text",
643
+ "html",
644
+ "attachments",
645
+ "replyToMessageId"
646
+ ]
647
+ },
648
+ "required": [
649
+ "to",
650
+ "subject",
651
+ "text"
652
+ ]
653
+ }
207
654
  }
208
655
  }
209
656
  },
210
657
  {
211
- "if": { "properties": { "operation": { "const": "gmail.message.send" } } },
658
+ "if": {
659
+ "properties": {
660
+ "operation": {
661
+ "const": "service.request"
662
+ }
663
+ }
664
+ },
212
665
  "then": {
213
666
  "x-read-only": false,
214
- "required": ["idempotencyKey"],
215
667
  "properties": {
216
668
  "mode": false,
217
- "input": { "propertyNames": { "enum": ["to", "cc", "bcc", "subject", "text", "html", "attachments", "replyToMessageId"] }, "required": ["to", "subject", "text"] }
669
+ "resource": false,
670
+ "input": {
671
+ "propertyNames": {
672
+ "enum": [
673
+ "method",
674
+ "path",
675
+ "pathParams",
676
+ "query",
677
+ "headers",
678
+ "body"
679
+ ]
680
+ },
681
+ "required": [
682
+ "method",
683
+ "path"
684
+ ]
685
+ }
686
+ },
687
+ "not": {
688
+ "required": [
689
+ "idempotencyKey"
690
+ ]
218
691
  }
692
+ },
693
+ "else": {
694
+ "required": [
695
+ "resource"
696
+ ]
219
697
  }
220
698
  }
221
699
  ],
222
700
  "definitions": {
223
- "EmailAddress": { "type": "string", "minLength": 3, "maxLength": 254, "pattern": "^[^\\s@<>,;\"]+@[^\\s@<>,;\"]+\\.[^\\s@<>,;\"]+$", "description": "One bare email address (no display name)." },
224
- "EmailAddressList": { "type": "array", "minItems": 1, "maxItems": 50, "uniqueItems": true, "items": { "$ref": "#/definitions/EmailAddress" } },
701
+ "EmailAddress": {
702
+ "type": "string",
703
+ "minLength": 3,
704
+ "maxLength": 254,
705
+ "pattern": "^[^\\s@<>,;\"]+@[^\\s@<>,;\"]+\\.[^\\s@<>,;\"]+$",
706
+ "description": "One bare email address (no display name)."
707
+ },
708
+ "EmailAddressList": {
709
+ "type": "array",
710
+ "minItems": 1,
711
+ "maxItems": 50,
712
+ "uniqueItems": true,
713
+ "items": {
714
+ "$ref": "#/definitions/EmailAddress"
715
+ }
716
+ },
225
717
  "SlackMessageFormat": {
226
718
  "type": "string",
227
- "enum": ["plain", "mrkdwn"],
719
+ "enum": [
720
+ "plain",
721
+ "mrkdwn"
722
+ ],
228
723
  "default": "plain",
229
724
  "description": "How Slack renders a message body (0.71.0): plain posts the text verbatim; mrkdwn lets Slack interpret its markup (*bold*, links, lists)."
230
725
  },
231
726
  "EmailAttachment": {
232
727
  "type": "object",
233
728
  "additionalProperties": false,
234
- "required": ["filename", "contentType", "dataBase64"],
729
+ "required": [
730
+ "filename",
731
+ "contentType",
732
+ "dataBase64"
733
+ ],
235
734
  "description": "One file attached to a gmail.message.send (0.71.0), carried inline as base64. At most 2 MiB of bytes, which is 2,796,203 base64 characters.",
236
735
  "properties": {
237
- "filename": { "type": "string", "minLength": 1, "maxLength": 255, "description": "The name the recipient sees." },
238
- "contentType": { "type": "string", "minLength": 1, "maxLength": 128, "description": "The MIME type, e.g. application/pdf." },
239
- "dataBase64": { "type": "string", "minLength": 1, "maxLength": 2796203, "description": "The file bytes, standard base64." }
736
+ "filename": {
737
+ "type": "string",
738
+ "minLength": 1,
739
+ "maxLength": 255,
740
+ "description": "The name the recipient sees."
741
+ },
742
+ "contentType": {
743
+ "type": "string",
744
+ "minLength": 1,
745
+ "maxLength": 128,
746
+ "description": "The MIME type, e.g. application/pdf."
747
+ },
748
+ "dataBase64": {
749
+ "type": "string",
750
+ "minLength": 1,
751
+ "maxLength": 2796203,
752
+ "description": "The file bytes, standard base64."
753
+ }
240
754
  }
241
755
  },
242
756
  "WarehouseFilter": {
243
757
  "type": "object",
244
758
  "additionalProperties": false,
245
- "required": ["column", "op"],
759
+ "required": [
760
+ "column",
761
+ "op"
762
+ ],
246
763
  "description": "One warehouse.view.read filter (0.70.0): <column> <op> <value>. The value is always bound as a query parameter, never written into the SQL. Which value an op takes is a governance rule: isNull/isNotNull take none; in/notIn take a non-empty list of one scalar type; every other op takes one scalar, and a boolean only with eq/ne.",
247
764
  "properties": {
248
- "column": { "type": "string", "pattern": "^[A-Za-z_][A-Za-z0-9_]{0,62}$", "description": "The filtered column; never `*`." },
249
- "op": { "$ref": "#/definitions/WarehouseFilterOp" },
765
+ "column": {
766
+ "type": "string",
767
+ "pattern": "^[A-Za-z_][A-Za-z0-9_]{0,62}$",
768
+ "description": "The filtered column; never `*`."
769
+ },
770
+ "op": {
771
+ "$ref": "#/definitions/WarehouseFilterOp"
772
+ },
250
773
  "value": {
251
774
  "description": "The value compared with: a string (a date or timestamp is its ISO string), a number, a boolean, or a list of strings or numbers for in/notIn. Never null; use isNull or isNotNull.",
252
775
  "oneOf": [
253
- { "type": "string", "maxLength": 256 },
254
- { "type": "number" },
255
- { "type": "boolean" },
256
- { "type": "array", "minItems": 1, "maxItems": 100, "items": { "oneOf": [{ "type": "string", "maxLength": 256 }, { "type": "number" }] } }
776
+ {
777
+ "type": "string",
778
+ "maxLength": 256
779
+ },
780
+ {
781
+ "type": "number"
782
+ },
783
+ {
784
+ "type": "boolean"
785
+ },
786
+ {
787
+ "type": "array",
788
+ "minItems": 1,
789
+ "maxItems": 100,
790
+ "items": {
791
+ "oneOf": [
792
+ {
793
+ "type": "string",
794
+ "maxLength": 256
795
+ },
796
+ {
797
+ "type": "number"
798
+ }
799
+ ]
800
+ }
801
+ }
257
802
  ]
258
803
  }
259
804
  }
260
805
  },
261
806
  "WarehouseFilterOp": {
262
807
  "type": "string",
263
- "enum": ["eq", "ne", "gt", "gte", "lt", "lte", "in", "notIn", "isNull", "isNotNull"],
808
+ "enum": [
809
+ "eq",
810
+ "ne",
811
+ "gt",
812
+ "gte",
813
+ "lt",
814
+ "lte",
815
+ "in",
816
+ "notIn",
817
+ "isNull",
818
+ "isNotNull"
819
+ ],
264
820
  "description": "A filter comparison: = <> > >= < <= IN, NOT IN, IS NULL, IS NOT NULL. Word-like so every value is a valid generated constant."
265
821
  },
266
822
  "WarehouseAggregate": {
267
823
  "type": "object",
268
824
  "additionalProperties": false,
269
- "required": ["fn", "column", "as"],
825
+ "required": [
826
+ "fn",
827
+ "column",
828
+ "as"
829
+ ],
270
830
  "description": "One computed column of an aggregate warehouse.view.read (0.70.0): fn(column) AS as. `*` is a column only for count (a governance rule). The value follows the read's cell rules, so a 64-bit or decimal result may be a string.",
271
831
  "properties": {
272
- "fn": { "$ref": "#/definitions/WarehouseAggregateFn" },
273
- "column": { "$ref": "app-integrations-declaration.schema.json#/definitions/ColumnName" },
274
- "as": { "type": "string", "pattern": "^[a-z_][a-z0-9_]{0,62}$", "description": "The name the computed value is returned under; unique in the read and never a groupBy column (governance rules)." }
832
+ "fn": {
833
+ "$ref": "#/definitions/WarehouseAggregateFn"
834
+ },
835
+ "column": {
836
+ "$ref": "app-integrations-declaration.schema.json#/definitions/ColumnName"
837
+ },
838
+ "as": {
839
+ "type": "string",
840
+ "pattern": "^[a-z_][a-z0-9_]{0,62}$",
841
+ "description": "The name the computed value is returned under; unique in the read and never a groupBy column (governance rules)."
842
+ }
275
843
  }
276
844
  },
277
845
  "WarehouseAggregateFn": {
278
846
  "type": "string",
279
- "enum": ["count", "sum", "avg", "min", "max"],
847
+ "enum": [
848
+ "count",
849
+ "sum",
850
+ "avg",
851
+ "min",
852
+ "max"
853
+ ],
280
854
  "description": "An aggregate function."
281
855
  },
282
856
  "WarehouseOrder": {
283
857
  "type": "object",
284
858
  "additionalProperties": false,
285
- "required": ["column"],
859
+ "required": [
860
+ "column"
861
+ ],
286
862
  "description": "One warehouse.view.read sort key (0.70.0). A plain read sorts by a selected column (any column when it selects `*`); an aggregate read sorts by a groupBy column or an aggregate alias (governance rules).",
287
863
  "properties": {
288
- "column": { "type": "string", "pattern": "^[A-Za-z_][A-Za-z0-9_]{0,62}$", "description": "A column or an aggregate alias; never `*`." },
289
- "direction": { "$ref": "#/definitions/WarehouseOrderDirection" }
864
+ "column": {
865
+ "type": "string",
866
+ "pattern": "^[A-Za-z_][A-Za-z0-9_]{0,62}$",
867
+ "description": "A column or an aggregate alias; never `*`."
868
+ },
869
+ "direction": {
870
+ "$ref": "#/definitions/WarehouseOrderDirection"
871
+ }
290
872
  }
291
873
  },
292
874
  "WarehouseOrderDirection": {
293
875
  "type": "string",
294
- "enum": ["asc", "desc"],
876
+ "enum": [
877
+ "asc",
878
+ "desc"
879
+ ],
295
880
  "default": "asc",
296
881
  "description": "Sort direction; asc when absent."
297
882
  }
298
883
  },
299
884
  "examples": [
300
- { "connection": "company-slack", "operation": "slack.channel.history", "resource": "team-updates", "input": { "limit": 15 } },
885
+ {
886
+ "connection": "company-slack",
887
+ "operation": "slack.channel.history",
888
+ "resource": "team-updates",
889
+ "input": {
890
+ "limit": 15
891
+ }
892
+ },
301
893
  {
302
894
  "connection": "company-slack",
303
895
  "operation": "slack.message.post",
304
896
  "resource": "team-updates",
305
- "input": { "text": "Weekly update posted." },
897
+ "input": {
898
+ "text": "Weekly update posted."
899
+ },
306
900
  "idempotencyKey": "6f1c2c1e-3a1c-4c3e-9f0e-2b6f7d8a9c10"
307
901
  },
308
- { "connection": "sales-warehouse", "operation": "warehouse.view.read", "resource": "weekly_sales", "input": { "columns": ["week", "total"], "limit": 100 } },
309
- { "connection": "gmail", "operation": "gmail.thread.list", "resource": "inbox", "input": { "limit": 15 } },
310
- { "connection": "gmail", "operation": "gmail.message.read", "resource": "inbox", "input": { "messageId": "18f2c3a4b5d6e7f8" } },
902
+ {
903
+ "connection": "sales-warehouse",
904
+ "operation": "warehouse.view.read",
905
+ "resource": "weekly_sales",
906
+ "input": {
907
+ "columns": [
908
+ "week",
909
+ "total"
910
+ ],
911
+ "limit": 100
912
+ }
913
+ },
914
+ {
915
+ "connection": "gmail",
916
+ "operation": "gmail.thread.list",
917
+ "resource": "inbox",
918
+ "input": {
919
+ "limit": 15
920
+ }
921
+ },
922
+ {
923
+ "connection": "gmail",
924
+ "operation": "gmail.message.read",
925
+ "resource": "inbox",
926
+ "input": {
927
+ "messageId": "18f2c3a4b5d6e7f8"
928
+ }
929
+ },
311
930
  {
312
931
  "connection": "gmail",
313
932
  "operation": "gmail.message.send",
314
933
  "resource": "inbox",
315
- "input": { "to": ["jane@example.com"], "subject": "Weekly report", "text": "Shipped the weekly report.", "replyToMessageId": "18f2c3a4b5d6e7f8" },
934
+ "input": {
935
+ "to": [
936
+ "jane@example.com"
937
+ ],
938
+ "subject": "Weekly report",
939
+ "text": "Shipped the weekly report.",
940
+ "replyToMessageId": "18f2c3a4b5d6e7f8"
941
+ },
316
942
  "idempotencyKey": "6f1c2c1e-3a1c-4c3e-9f0e-2b6f7d8a9c11"
317
943
  },
318
944
  {
319
945
  "connection": "company-slack",
320
946
  "operation": "slack.message.post",
321
947
  "resource": "team-updates",
322
- "input": { "text": "Lunch is at noon." },
948
+ "input": {
949
+ "text": "Lunch is at noon."
950
+ },
323
951
  "idempotencyKey": "6f1c2c1e-3a1c-4c3e-9f0e-2b6f7d8a9c12",
324
952
  "mode": "user"
325
953
  },
@@ -328,12 +956,34 @@
328
956
  "operation": "warehouse.view.read",
329
957
  "resource": "invoices",
330
958
  "input": {
331
- "columns": ["invoice_id", "region_code", "amount_minor"],
959
+ "columns": [
960
+ "invoice_id",
961
+ "region_code",
962
+ "amount_minor"
963
+ ],
332
964
  "where": [
333
- { "column": "status", "op": "eq", "value": "PAID" },
334
- { "column": "invoice_date", "op": "gte", "value": "2026-02-01" },
335
- { "column": "currency", "op": "in", "value": ["USD", "EUR"] },
336
- { "column": "closed_at", "op": "isNull" }
965
+ {
966
+ "column": "status",
967
+ "op": "eq",
968
+ "value": "PAID"
969
+ },
970
+ {
971
+ "column": "invoice_date",
972
+ "op": "gte",
973
+ "value": "2026-02-01"
974
+ },
975
+ {
976
+ "column": "currency",
977
+ "op": "in",
978
+ "value": [
979
+ "USD",
980
+ "EUR"
981
+ ]
982
+ },
983
+ {
984
+ "column": "closed_at",
985
+ "op": "isNull"
986
+ }
337
987
  ],
338
988
  "limit": 500
339
989
  }
@@ -343,13 +993,34 @@
343
993
  "operation": "warehouse.view.read",
344
994
  "resource": "invoices",
345
995
  "input": {
346
- "where": [{ "column": "status", "op": "eq", "value": "PAID" }],
347
- "groupBy": ["region_code"],
996
+ "where": [
997
+ {
998
+ "column": "status",
999
+ "op": "eq",
1000
+ "value": "PAID"
1001
+ }
1002
+ ],
1003
+ "groupBy": [
1004
+ "region_code"
1005
+ ],
348
1006
  "aggregates": [
349
- { "fn": "sum", "column": "amount_minor", "as": "total_minor" },
350
- { "fn": "count", "column": "*", "as": "invoices" }
1007
+ {
1008
+ "fn": "sum",
1009
+ "column": "amount_minor",
1010
+ "as": "total_minor"
1011
+ },
1012
+ {
1013
+ "fn": "count",
1014
+ "column": "*",
1015
+ "as": "invoices"
1016
+ }
1017
+ ],
1018
+ "orderBy": [
1019
+ {
1020
+ "column": "total_minor",
1021
+ "direction": "desc"
1022
+ }
351
1023
  ],
352
- "orderBy": [{ "column": "total_minor", "direction": "desc" }],
353
1024
  "limit": 100
354
1025
  }
355
1026
  },
@@ -357,42 +1028,156 @@
357
1028
  "connection": "company-slack",
358
1029
  "operation": "slack.message.update",
359
1030
  "resource": "team-updates",
360
- "input": { "messageId": "1725840100.000200", "text": "Weekly update posted *(corrected)*.", "format": "mrkdwn" },
1031
+ "input": {
1032
+ "messageId": "1725840100.000200",
1033
+ "text": "Weekly update posted *(corrected)*.",
1034
+ "format": "mrkdwn"
1035
+ },
361
1036
  "mode": "app"
362
1037
  },
363
- { "connection": "company-slack", "operation": "slack.message.delete", "resource": "team-updates", "input": { "messageId": "1725840100.000200" } },
1038
+ {
1039
+ "connection": "company-slack",
1040
+ "operation": "slack.message.delete",
1041
+ "resource": "team-updates",
1042
+ "input": {
1043
+ "messageId": "1725840100.000200"
1044
+ }
1045
+ },
364
1046
  {
365
1047
  "connection": "company-slack",
366
1048
  "operation": "slack.dm.send",
367
1049
  "resource": "workspace",
368
- "input": { "to": ["jane@example.com"], "text": "Your expense report was approved." },
1050
+ "input": {
1051
+ "to": [
1052
+ "jane@example.com"
1053
+ ],
1054
+ "text": "Your expense report was approved."
1055
+ },
369
1056
  "idempotencyKey": "6f1c2c1e-3a1c-4c3e-9f0e-2b6f7d8a9c13"
370
1057
  },
371
- { "connection": "company-slack", "operation": "slack.user.lookup", "resource": "workspace", "input": { "email": "jane@example.com" } },
1058
+ {
1059
+ "connection": "company-slack",
1060
+ "operation": "slack.user.lookup",
1061
+ "resource": "workspace",
1062
+ "input": {
1063
+ "email": "jane@example.com"
1064
+ }
1065
+ },
372
1066
  {
373
1067
  "connection": "company-slack",
374
1068
  "operation": "slack.message.post",
375
1069
  "resource": "team-updates",
376
- "input": { "text": "Thanks, shipped.", "threadId": "1725840000.000100", "format": "plain" },
1070
+ "input": {
1071
+ "text": "Thanks, shipped.",
1072
+ "threadId": "1725840000.000100",
1073
+ "format": "plain"
1074
+ },
377
1075
  "idempotencyKey": "6f1c2c1e-3a1c-4c3e-9f0e-2b6f7d8a9c14",
378
1076
  "mode": "user"
379
1077
  },
380
- { "connection": "company-slack", "operation": "slack.channel.history", "resource": "team-updates", "input": { "limit": 100, "cursor": "bmV4dF90czoxNzI1ODQwMDAwMDAwMTAw", "oldest": "1725750000.000000" }, "mode": "app" },
381
- { "connection": "gmail", "operation": "gmail.thread.list", "resource": "inbox", "input": { "limit": 50, "q": "from:jane@example.com newer_than:7d", "labelIds": ["INBOX", "IMPORTANT"] } },
382
- { "connection": "gmail", "operation": "gmail.thread.read", "resource": "inbox", "input": { "threadId": "18f2c3a4b5d6e7f0" } },
383
- { "connection": "gmail", "operation": "gmail.attachment.read", "resource": "inbox", "input": { "messageId": "18f2c3a4b5d6e7f8", "attachmentId": "ANGjdJ8w9h1Q2k3L4m5N6o7P8q9R0s" } },
1078
+ {
1079
+ "connection": "company-slack",
1080
+ "operation": "slack.channel.history",
1081
+ "resource": "team-updates",
1082
+ "input": {
1083
+ "limit": 100,
1084
+ "cursor": "bmV4dF90czoxNzI1ODQwMDAwMDAwMTAw",
1085
+ "oldest": "1725750000.000000"
1086
+ },
1087
+ "mode": "app"
1088
+ },
1089
+ {
1090
+ "connection": "gmail",
1091
+ "operation": "gmail.thread.list",
1092
+ "resource": "inbox",
1093
+ "input": {
1094
+ "limit": 50,
1095
+ "q": "from:jane@example.com newer_than:7d",
1096
+ "labelIds": [
1097
+ "INBOX",
1098
+ "IMPORTANT"
1099
+ ]
1100
+ }
1101
+ },
1102
+ {
1103
+ "connection": "gmail",
1104
+ "operation": "gmail.thread.read",
1105
+ "resource": "inbox",
1106
+ "input": {
1107
+ "threadId": "18f2c3a4b5d6e7f0"
1108
+ }
1109
+ },
1110
+ {
1111
+ "connection": "gmail",
1112
+ "operation": "gmail.attachment.read",
1113
+ "resource": "inbox",
1114
+ "input": {
1115
+ "messageId": "18f2c3a4b5d6e7f8",
1116
+ "attachmentId": "ANGjdJ8w9h1Q2k3L4m5N6o7P8q9R0s"
1117
+ }
1118
+ },
384
1119
  {
385
1120
  "connection": "gmail",
386
1121
  "operation": "gmail.message.send",
387
1122
  "resource": "inbox",
388
1123
  "input": {
389
- "to": ["jane@example.com"],
1124
+ "to": [
1125
+ "jane@example.com"
1126
+ ],
390
1127
  "subject": "Weekly report",
391
1128
  "text": "The weekly report is attached.",
392
1129
  "html": "<p>The weekly report is <b>attached</b>.</p>",
393
- "attachments": [{ "filename": "weekly-report.pdf", "contentType": "application/pdf", "dataBase64": "JVBERi0xLjQKJcOkw7zDtsOfCg==" }]
1130
+ "attachments": [
1131
+ {
1132
+ "filename": "weekly-report.pdf",
1133
+ "contentType": "application/pdf",
1134
+ "dataBase64": "JVBERi0xLjQKJcOkw7zDtsOfCg=="
1135
+ }
1136
+ ]
394
1137
  },
395
1138
  "idempotencyKey": "6f1c2c1e-3a1c-4c3e-9f0e-2b6f7d8a9c15"
1139
+ },
1140
+ {
1141
+ "connection": "inventory",
1142
+ "operation": "service.request",
1143
+ "input": {
1144
+ "method": "POST",
1145
+ "path": "orders/{id}",
1146
+ "pathParams": {
1147
+ "id": "42"
1148
+ },
1149
+ "query": {
1150
+ "notify": true
1151
+ },
1152
+ "headers": {
1153
+ "X-Trace": "test"
1154
+ },
1155
+ "body": {
1156
+ "text": "approved"
1157
+ }
1158
+ }
1159
+ },
1160
+ {
1161
+ "connection": "sales-warehouse",
1162
+ "operation": "warehouse.view.read",
1163
+ "resource": "weekly_sales",
1164
+ "input": {
1165
+ "columns": [
1166
+ "week",
1167
+ "total"
1168
+ ],
1169
+ "limit": 100
1170
+ },
1171
+ "sampleRows": [
1172
+ {
1173
+ "week": "2026-W36",
1174
+ "total": "1234.50"
1175
+ },
1176
+ {
1177
+ "week": "2026-W37",
1178
+ "total": null
1179
+ }
1180
+ ]
396
1181
  }
397
1182
  ]
398
1183
  }