@fourier-labs/harbour-contracts 0.71.0 → 0.73.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 (34) hide show
  1. package/contract-release.json +26 -24
  2. package/dist/gen-ts/app-integrations-declaration.d.ts +29 -20
  3. package/dist/gen-ts/contract-release.d.ts +6 -6
  4. package/dist/gen-ts/contract-release.js +6 -6
  5. package/dist/gen-ts/data-grant-v2.d.ts +2 -2
  6. package/dist/gen-ts/deployment-app-registration.d.ts +1 -1
  7. package/dist/gen-ts/deployment-preview-retirement.d.ts +1 -1
  8. package/dist/gen-ts/index.d.ts +1 -0
  9. package/dist/gen-ts/index.js +1 -0
  10. package/dist/gen-ts/integration-execute-input-rules.d.ts +35 -0
  11. package/dist/gen-ts/integration-execute-input-rules.js +15 -0
  12. package/dist/gen-ts/integration-execute-request.d.ts +16 -2
  13. package/dist/gen-ts/integration-execute-result.d.ts +5 -0
  14. package/dist/gen-ts/integration-operations.d.ts +94 -0
  15. package/dist/gen-ts/integration-operations.js +126 -0
  16. package/dist/gen-ts/saas-connection.d.ts +2 -2
  17. package/gen-ts/app-integrations-declaration.ts +29 -20
  18. package/gen-ts/contract-release.ts +6 -6
  19. package/gen-ts/data-grant-v2.ts +2 -1
  20. package/gen-ts/deployment-app-registration.ts +1 -1
  21. package/gen-ts/deployment-preview-retirement.ts +1 -1
  22. package/gen-ts/index.ts +1 -0
  23. package/gen-ts/integration-execute-input-rules.ts +15 -0
  24. package/gen-ts/integration-execute-request.ts +17 -2
  25. package/gen-ts/integration-execute-result.ts +5 -0
  26. package/gen-ts/integration-operations.ts +127 -0
  27. package/gen-ts/saas-connection.ts +2 -1
  28. package/package.json +1 -1
  29. package/schema/app-integrations-declaration.schema.json +478 -67
  30. package/schema/deployment-app-registration.schema.json +1 -1
  31. package/schema/deployment-preview-retirement.schema.json +1 -1
  32. package/schema/integration-execute-request.schema.json +884 -120
  33. package/schema/integration-execute-result.schema.json +505 -82
  34. package/schema/saas-connection.schema.json +1 -1
@@ -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,228 +243,699 @@
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."
89
247
  }
90
248
  },
91
249
  "allOf": [
92
250
  {
93
- "if": { "properties": { "operation": { "const": "slack.channel.history" } } },
251
+ "if": {
252
+ "properties": {
253
+ "operation": {
254
+ "const": "slack.channel.history"
255
+ }
256
+ }
257
+ },
94
258
  "then": {
95
- "not": { "required": ["idempotencyKey"] },
259
+ "x-read-only": true,
260
+ "x-primary-identity": "user",
261
+ "not": {
262
+ "required": [
263
+ "idempotencyKey"
264
+ ]
265
+ },
96
266
  "properties": {
97
- "input": { "propertyNames": { "enum": ["limit", "cursor", "oldest", "latest"] }, "properties": { "limit": { "maximum": 100, "default": 15 } } }
267
+ "input": {
268
+ "propertyNames": {
269
+ "enum": [
270
+ "limit",
271
+ "cursor",
272
+ "oldest",
273
+ "latest"
274
+ ]
275
+ },
276
+ "properties": {
277
+ "limit": {
278
+ "maximum": 100,
279
+ "default": 15
280
+ }
281
+ }
282
+ }
98
283
  }
99
284
  }
100
285
  },
101
286
  {
102
- "if": { "properties": { "operation": { "const": "slack.message.post" } } },
287
+ "if": {
288
+ "properties": {
289
+ "operation": {
290
+ "const": "slack.message.post"
291
+ }
292
+ }
293
+ },
103
294
  "then": {
104
- "required": ["idempotencyKey"],
295
+ "x-read-only": false,
296
+ "required": [
297
+ "idempotencyKey"
298
+ ],
105
299
  "properties": {
106
- "input": { "propertyNames": { "enum": ["text", "threadId", "format"] }, "required": ["text"] }
300
+ "input": {
301
+ "propertyNames": {
302
+ "enum": [
303
+ "text",
304
+ "threadId",
305
+ "format"
306
+ ]
307
+ },
308
+ "required": [
309
+ "text"
310
+ ]
311
+ }
107
312
  }
108
313
  }
109
314
  },
110
315
  {
111
- "if": { "properties": { "operation": { "const": "slack.message.update" } } },
316
+ "if": {
317
+ "properties": {
318
+ "operation": {
319
+ "const": "slack.message.update"
320
+ }
321
+ }
322
+ },
112
323
  "then": {
113
- "not": { "required": ["idempotencyKey"] },
324
+ "x-read-only": false,
325
+ "not": {
326
+ "required": [
327
+ "idempotencyKey"
328
+ ]
329
+ },
114
330
  "properties": {
115
- "input": { "propertyNames": { "enum": ["messageId", "text", "format"] }, "required": ["messageId", "text"] }
331
+ "input": {
332
+ "propertyNames": {
333
+ "enum": [
334
+ "messageId",
335
+ "text",
336
+ "format"
337
+ ]
338
+ },
339
+ "required": [
340
+ "messageId",
341
+ "text"
342
+ ]
343
+ }
116
344
  }
117
345
  }
118
346
  },
119
347
  {
120
- "if": { "properties": { "operation": { "const": "slack.message.delete" } } },
348
+ "if": {
349
+ "properties": {
350
+ "operation": {
351
+ "const": "slack.message.delete"
352
+ }
353
+ }
354
+ },
121
355
  "then": {
122
- "not": { "required": ["idempotencyKey"] },
356
+ "x-read-only": false,
357
+ "not": {
358
+ "required": [
359
+ "idempotencyKey"
360
+ ]
361
+ },
123
362
  "properties": {
124
- "input": { "propertyNames": { "enum": ["messageId"] }, "required": ["messageId"] }
363
+ "input": {
364
+ "propertyNames": {
365
+ "enum": [
366
+ "messageId"
367
+ ]
368
+ },
369
+ "required": [
370
+ "messageId"
371
+ ]
372
+ }
125
373
  }
126
374
  }
127
375
  },
128
376
  {
129
- "if": { "properties": { "operation": { "const": "slack.dm.send" } } },
377
+ "if": {
378
+ "properties": {
379
+ "operation": {
380
+ "const": "slack.dm.send"
381
+ }
382
+ }
383
+ },
384
+ "then": {
385
+ "x-read-only": false,
386
+ "required": [
387
+ "idempotencyKey"
388
+ ],
389
+ "properties": {
390
+ "mode": false,
391
+ "input": {
392
+ "propertyNames": {
393
+ "enum": [
394
+ "to",
395
+ "text",
396
+ "format"
397
+ ]
398
+ },
399
+ "required": [
400
+ "to",
401
+ "text"
402
+ ],
403
+ "properties": {
404
+ "to": {
405
+ "maxItems": 8
406
+ }
407
+ }
408
+ }
409
+ }
410
+ }
411
+ },
412
+ {
413
+ "if": {
414
+ "properties": {
415
+ "operation": {
416
+ "const": "slack.user.lookup"
417
+ }
418
+ }
419
+ },
130
420
  "then": {
131
- "required": ["idempotencyKey"],
421
+ "x-read-only": true,
422
+ "not": {
423
+ "required": [
424
+ "idempotencyKey"
425
+ ]
426
+ },
132
427
  "properties": {
133
428
  "mode": false,
134
- "input": { "propertyNames": { "enum": ["to", "text", "format"] }, "required": ["to", "text"], "properties": { "to": { "maxItems": 8 } } }
429
+ "input": {
430
+ "propertyNames": {
431
+ "enum": [
432
+ "email"
433
+ ]
434
+ },
435
+ "required": [
436
+ "email"
437
+ ]
438
+ }
135
439
  }
136
440
  }
137
441
  },
138
442
  {
139
- "if": { "properties": { "operation": { "const": "slack.user.lookup" } } },
443
+ "if": {
444
+ "properties": {
445
+ "operation": {
446
+ "const": "warehouse.view.read"
447
+ }
448
+ }
449
+ },
140
450
  "then": {
141
- "not": { "required": ["idempotencyKey"] },
451
+ "x-read-only": true,
452
+ "not": {
453
+ "required": [
454
+ "idempotencyKey"
455
+ ]
456
+ },
142
457
  "properties": {
143
458
  "mode": false,
144
- "input": { "propertyNames": { "enum": ["email"] }, "required": ["email"] }
459
+ "input": {
460
+ "propertyNames": {
461
+ "enum": [
462
+ "columns",
463
+ "where",
464
+ "groupBy",
465
+ "aggregates",
466
+ "orderBy",
467
+ "limit"
468
+ ]
469
+ },
470
+ "properties": {
471
+ "limit": {
472
+ "maximum": 1000,
473
+ "default": 100
474
+ }
475
+ }
476
+ }
145
477
  }
146
478
  }
147
479
  },
148
480
  {
149
- "if": { "properties": { "operation": { "const": "warehouse.view.read" } } },
481
+ "if": {
482
+ "properties": {
483
+ "operation": {
484
+ "const": "gmail.thread.list"
485
+ }
486
+ }
487
+ },
150
488
  "then": {
151
- "not": { "required": ["idempotencyKey"] },
489
+ "x-read-only": true,
490
+ "not": {
491
+ "required": [
492
+ "idempotencyKey"
493
+ ]
494
+ },
152
495
  "properties": {
153
496
  "mode": false,
154
- "input": { "propertyNames": { "enum": ["columns", "where", "groupBy", "aggregates", "orderBy", "limit"] }, "properties": { "limit": { "maximum": 1000, "default": 100 } } }
497
+ "input": {
498
+ "propertyNames": {
499
+ "enum": [
500
+ "limit",
501
+ "q",
502
+ "labelIds",
503
+ "pageToken"
504
+ ]
505
+ },
506
+ "properties": {
507
+ "limit": {
508
+ "maximum": 50,
509
+ "default": 15
510
+ }
511
+ }
512
+ }
155
513
  }
156
514
  }
157
515
  },
158
516
  {
159
- "if": { "properties": { "operation": { "const": "gmail.thread.list" } } },
517
+ "if": {
518
+ "properties": {
519
+ "operation": {
520
+ "const": "gmail.thread.read"
521
+ }
522
+ }
523
+ },
160
524
  "then": {
161
- "not": { "required": ["idempotencyKey"] },
525
+ "x-read-only": true,
526
+ "not": {
527
+ "required": [
528
+ "idempotencyKey"
529
+ ]
530
+ },
162
531
  "properties": {
163
532
  "mode": false,
164
- "input": { "propertyNames": { "enum": ["limit", "q", "labelIds", "pageToken"] }, "properties": { "limit": { "maximum": 50, "default": 15 } } }
533
+ "input": {
534
+ "propertyNames": {
535
+ "enum": [
536
+ "threadId"
537
+ ]
538
+ },
539
+ "required": [
540
+ "threadId"
541
+ ]
542
+ }
165
543
  }
166
544
  }
167
545
  },
168
546
  {
169
- "if": { "properties": { "operation": { "const": "gmail.thread.read" } } },
547
+ "if": {
548
+ "properties": {
549
+ "operation": {
550
+ "const": "gmail.message.read"
551
+ }
552
+ }
553
+ },
170
554
  "then": {
171
- "not": { "required": ["idempotencyKey"] },
555
+ "x-read-only": true,
556
+ "not": {
557
+ "required": [
558
+ "idempotencyKey"
559
+ ]
560
+ },
172
561
  "properties": {
173
562
  "mode": false,
174
- "input": { "propertyNames": { "enum": ["threadId"] }, "required": ["threadId"] }
563
+ "input": {
564
+ "propertyNames": {
565
+ "enum": [
566
+ "messageId"
567
+ ]
568
+ },
569
+ "required": [
570
+ "messageId"
571
+ ]
572
+ }
175
573
  }
176
574
  }
177
575
  },
178
576
  {
179
- "if": { "properties": { "operation": { "const": "gmail.message.read" } } },
577
+ "if": {
578
+ "properties": {
579
+ "operation": {
580
+ "const": "gmail.attachment.read"
581
+ }
582
+ }
583
+ },
180
584
  "then": {
181
- "not": { "required": ["idempotencyKey"] },
585
+ "x-read-only": true,
586
+ "not": {
587
+ "required": [
588
+ "idempotencyKey"
589
+ ]
590
+ },
182
591
  "properties": {
183
592
  "mode": false,
184
- "input": { "propertyNames": { "enum": ["messageId"] }, "required": ["messageId"] }
593
+ "input": {
594
+ "propertyNames": {
595
+ "enum": [
596
+ "messageId",
597
+ "attachmentId"
598
+ ]
599
+ },
600
+ "required": [
601
+ "messageId",
602
+ "attachmentId"
603
+ ]
604
+ }
185
605
  }
186
606
  }
187
607
  },
188
608
  {
189
- "if": { "properties": { "operation": { "const": "gmail.attachment.read" } } },
609
+ "if": {
610
+ "properties": {
611
+ "operation": {
612
+ "const": "gmail.message.send"
613
+ }
614
+ }
615
+ },
190
616
  "then": {
191
- "not": { "required": ["idempotencyKey"] },
617
+ "x-read-only": false,
618
+ "required": [
619
+ "idempotencyKey"
620
+ ],
192
621
  "properties": {
193
622
  "mode": false,
194
- "input": { "propertyNames": { "enum": ["messageId", "attachmentId"] }, "required": ["messageId", "attachmentId"] }
623
+ "input": {
624
+ "propertyNames": {
625
+ "enum": [
626
+ "to",
627
+ "cc",
628
+ "bcc",
629
+ "subject",
630
+ "text",
631
+ "html",
632
+ "attachments",
633
+ "replyToMessageId"
634
+ ]
635
+ },
636
+ "required": [
637
+ "to",
638
+ "subject",
639
+ "text"
640
+ ]
641
+ }
195
642
  }
196
643
  }
197
644
  },
198
645
  {
199
- "if": { "properties": { "operation": { "const": "gmail.message.send" } } },
646
+ "if": {
647
+ "properties": {
648
+ "operation": {
649
+ "const": "service.request"
650
+ }
651
+ }
652
+ },
200
653
  "then": {
201
- "required": ["idempotencyKey"],
654
+ "x-read-only": false,
202
655
  "properties": {
203
656
  "mode": false,
204
- "input": { "propertyNames": { "enum": ["to", "cc", "bcc", "subject", "text", "html", "attachments", "replyToMessageId"] }, "required": ["to", "subject", "text"] }
657
+ "resource": false,
658
+ "input": {
659
+ "propertyNames": {
660
+ "enum": [
661
+ "method",
662
+ "path",
663
+ "pathParams",
664
+ "query",
665
+ "headers",
666
+ "body"
667
+ ]
668
+ },
669
+ "required": [
670
+ "method",
671
+ "path"
672
+ ]
673
+ }
674
+ },
675
+ "not": {
676
+ "required": [
677
+ "idempotencyKey"
678
+ ]
205
679
  }
680
+ },
681
+ "else": {
682
+ "required": [
683
+ "resource"
684
+ ]
206
685
  }
207
686
  }
208
687
  ],
209
688
  "definitions": {
210
- "EmailAddress": { "type": "string", "minLength": 3, "maxLength": 254, "pattern": "^[^\\s@<>,;\"]+@[^\\s@<>,;\"]+\\.[^\\s@<>,;\"]+$", "description": "One bare email address (no display name)." },
211
- "EmailAddressList": { "type": "array", "minItems": 1, "maxItems": 50, "uniqueItems": true, "items": { "$ref": "#/definitions/EmailAddress" } },
689
+ "EmailAddress": {
690
+ "type": "string",
691
+ "minLength": 3,
692
+ "maxLength": 254,
693
+ "pattern": "^[^\\s@<>,;\"]+@[^\\s@<>,;\"]+\\.[^\\s@<>,;\"]+$",
694
+ "description": "One bare email address (no display name)."
695
+ },
696
+ "EmailAddressList": {
697
+ "type": "array",
698
+ "minItems": 1,
699
+ "maxItems": 50,
700
+ "uniqueItems": true,
701
+ "items": {
702
+ "$ref": "#/definitions/EmailAddress"
703
+ }
704
+ },
212
705
  "SlackMessageFormat": {
213
706
  "type": "string",
214
- "enum": ["plain", "mrkdwn"],
707
+ "enum": [
708
+ "plain",
709
+ "mrkdwn"
710
+ ],
215
711
  "default": "plain",
216
712
  "description": "How Slack renders a message body (0.71.0): plain posts the text verbatim; mrkdwn lets Slack interpret its markup (*bold*, links, lists)."
217
713
  },
218
714
  "EmailAttachment": {
219
715
  "type": "object",
220
716
  "additionalProperties": false,
221
- "required": ["filename", "contentType", "dataBase64"],
717
+ "required": [
718
+ "filename",
719
+ "contentType",
720
+ "dataBase64"
721
+ ],
222
722
  "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.",
223
723
  "properties": {
224
- "filename": { "type": "string", "minLength": 1, "maxLength": 255, "description": "The name the recipient sees." },
225
- "contentType": { "type": "string", "minLength": 1, "maxLength": 128, "description": "The MIME type, e.g. application/pdf." },
226
- "dataBase64": { "type": "string", "minLength": 1, "maxLength": 2796203, "description": "The file bytes, standard base64." }
724
+ "filename": {
725
+ "type": "string",
726
+ "minLength": 1,
727
+ "maxLength": 255,
728
+ "description": "The name the recipient sees."
729
+ },
730
+ "contentType": {
731
+ "type": "string",
732
+ "minLength": 1,
733
+ "maxLength": 128,
734
+ "description": "The MIME type, e.g. application/pdf."
735
+ },
736
+ "dataBase64": {
737
+ "type": "string",
738
+ "minLength": 1,
739
+ "maxLength": 2796203,
740
+ "description": "The file bytes, standard base64."
741
+ }
227
742
  }
228
743
  },
229
744
  "WarehouseFilter": {
230
745
  "type": "object",
231
746
  "additionalProperties": false,
232
- "required": ["column", "op"],
747
+ "required": [
748
+ "column",
749
+ "op"
750
+ ],
233
751
  "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.",
234
752
  "properties": {
235
- "column": { "type": "string", "pattern": "^[A-Za-z_][A-Za-z0-9_]{0,62}$", "description": "The filtered column; never `*`." },
236
- "op": { "$ref": "#/definitions/WarehouseFilterOp" },
753
+ "column": {
754
+ "type": "string",
755
+ "pattern": "^[A-Za-z_][A-Za-z0-9_]{0,62}$",
756
+ "description": "The filtered column; never `*`."
757
+ },
758
+ "op": {
759
+ "$ref": "#/definitions/WarehouseFilterOp"
760
+ },
237
761
  "value": {
238
762
  "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.",
239
763
  "oneOf": [
240
- { "type": "string", "maxLength": 256 },
241
- { "type": "number" },
242
- { "type": "boolean" },
243
- { "type": "array", "minItems": 1, "maxItems": 100, "items": { "oneOf": [{ "type": "string", "maxLength": 256 }, { "type": "number" }] } }
764
+ {
765
+ "type": "string",
766
+ "maxLength": 256
767
+ },
768
+ {
769
+ "type": "number"
770
+ },
771
+ {
772
+ "type": "boolean"
773
+ },
774
+ {
775
+ "type": "array",
776
+ "minItems": 1,
777
+ "maxItems": 100,
778
+ "items": {
779
+ "oneOf": [
780
+ {
781
+ "type": "string",
782
+ "maxLength": 256
783
+ },
784
+ {
785
+ "type": "number"
786
+ }
787
+ ]
788
+ }
789
+ }
244
790
  ]
245
791
  }
246
792
  }
247
793
  },
248
794
  "WarehouseFilterOp": {
249
795
  "type": "string",
250
- "enum": ["eq", "ne", "gt", "gte", "lt", "lte", "in", "notIn", "isNull", "isNotNull"],
796
+ "enum": [
797
+ "eq",
798
+ "ne",
799
+ "gt",
800
+ "gte",
801
+ "lt",
802
+ "lte",
803
+ "in",
804
+ "notIn",
805
+ "isNull",
806
+ "isNotNull"
807
+ ],
251
808
  "description": "A filter comparison: = <> > >= < <= IN, NOT IN, IS NULL, IS NOT NULL. Word-like so every value is a valid generated constant."
252
809
  },
253
810
  "WarehouseAggregate": {
254
811
  "type": "object",
255
812
  "additionalProperties": false,
256
- "required": ["fn", "column", "as"],
813
+ "required": [
814
+ "fn",
815
+ "column",
816
+ "as"
817
+ ],
257
818
  "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.",
258
819
  "properties": {
259
- "fn": { "$ref": "#/definitions/WarehouseAggregateFn" },
260
- "column": { "$ref": "app-integrations-declaration.schema.json#/definitions/ColumnName" },
261
- "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)." }
820
+ "fn": {
821
+ "$ref": "#/definitions/WarehouseAggregateFn"
822
+ },
823
+ "column": {
824
+ "$ref": "app-integrations-declaration.schema.json#/definitions/ColumnName"
825
+ },
826
+ "as": {
827
+ "type": "string",
828
+ "pattern": "^[a-z_][a-z0-9_]{0,62}$",
829
+ "description": "The name the computed value is returned under; unique in the read and never a groupBy column (governance rules)."
830
+ }
262
831
  }
263
832
  },
264
833
  "WarehouseAggregateFn": {
265
834
  "type": "string",
266
- "enum": ["count", "sum", "avg", "min", "max"],
835
+ "enum": [
836
+ "count",
837
+ "sum",
838
+ "avg",
839
+ "min",
840
+ "max"
841
+ ],
267
842
  "description": "An aggregate function."
268
843
  },
269
844
  "WarehouseOrder": {
270
845
  "type": "object",
271
846
  "additionalProperties": false,
272
- "required": ["column"],
847
+ "required": [
848
+ "column"
849
+ ],
273
850
  "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).",
274
851
  "properties": {
275
- "column": { "type": "string", "pattern": "^[A-Za-z_][A-Za-z0-9_]{0,62}$", "description": "A column or an aggregate alias; never `*`." },
276
- "direction": { "$ref": "#/definitions/WarehouseOrderDirection" }
852
+ "column": {
853
+ "type": "string",
854
+ "pattern": "^[A-Za-z_][A-Za-z0-9_]{0,62}$",
855
+ "description": "A column or an aggregate alias; never `*`."
856
+ },
857
+ "direction": {
858
+ "$ref": "#/definitions/WarehouseOrderDirection"
859
+ }
277
860
  }
278
861
  },
279
862
  "WarehouseOrderDirection": {
280
863
  "type": "string",
281
- "enum": ["asc", "desc"],
864
+ "enum": [
865
+ "asc",
866
+ "desc"
867
+ ],
282
868
  "default": "asc",
283
869
  "description": "Sort direction; asc when absent."
284
870
  }
285
871
  },
286
872
  "examples": [
287
- { "connection": "company-slack", "operation": "slack.channel.history", "resource": "team-updates", "input": { "limit": 15 } },
873
+ {
874
+ "connection": "company-slack",
875
+ "operation": "slack.channel.history",
876
+ "resource": "team-updates",
877
+ "input": {
878
+ "limit": 15
879
+ }
880
+ },
288
881
  {
289
882
  "connection": "company-slack",
290
883
  "operation": "slack.message.post",
291
884
  "resource": "team-updates",
292
- "input": { "text": "Weekly update posted." },
885
+ "input": {
886
+ "text": "Weekly update posted."
887
+ },
293
888
  "idempotencyKey": "6f1c2c1e-3a1c-4c3e-9f0e-2b6f7d8a9c10"
294
889
  },
295
- { "connection": "sales-warehouse", "operation": "warehouse.view.read", "resource": "weekly_sales", "input": { "columns": ["week", "total"], "limit": 100 } },
296
- { "connection": "gmail", "operation": "gmail.thread.list", "resource": "inbox", "input": { "limit": 15 } },
297
- { "connection": "gmail", "operation": "gmail.message.read", "resource": "inbox", "input": { "messageId": "18f2c3a4b5d6e7f8" } },
890
+ {
891
+ "connection": "sales-warehouse",
892
+ "operation": "warehouse.view.read",
893
+ "resource": "weekly_sales",
894
+ "input": {
895
+ "columns": [
896
+ "week",
897
+ "total"
898
+ ],
899
+ "limit": 100
900
+ }
901
+ },
902
+ {
903
+ "connection": "gmail",
904
+ "operation": "gmail.thread.list",
905
+ "resource": "inbox",
906
+ "input": {
907
+ "limit": 15
908
+ }
909
+ },
910
+ {
911
+ "connection": "gmail",
912
+ "operation": "gmail.message.read",
913
+ "resource": "inbox",
914
+ "input": {
915
+ "messageId": "18f2c3a4b5d6e7f8"
916
+ }
917
+ },
298
918
  {
299
919
  "connection": "gmail",
300
920
  "operation": "gmail.message.send",
301
921
  "resource": "inbox",
302
- "input": { "to": ["jane@example.com"], "subject": "Weekly report", "text": "Shipped the weekly report.", "replyToMessageId": "18f2c3a4b5d6e7f8" },
922
+ "input": {
923
+ "to": [
924
+ "jane@example.com"
925
+ ],
926
+ "subject": "Weekly report",
927
+ "text": "Shipped the weekly report.",
928
+ "replyToMessageId": "18f2c3a4b5d6e7f8"
929
+ },
303
930
  "idempotencyKey": "6f1c2c1e-3a1c-4c3e-9f0e-2b6f7d8a9c11"
304
931
  },
305
932
  {
306
933
  "connection": "company-slack",
307
934
  "operation": "slack.message.post",
308
935
  "resource": "team-updates",
309
- "input": { "text": "Lunch is at noon." },
936
+ "input": {
937
+ "text": "Lunch is at noon."
938
+ },
310
939
  "idempotencyKey": "6f1c2c1e-3a1c-4c3e-9f0e-2b6f7d8a9c12",
311
940
  "mode": "user"
312
941
  },
@@ -315,12 +944,34 @@
315
944
  "operation": "warehouse.view.read",
316
945
  "resource": "invoices",
317
946
  "input": {
318
- "columns": ["invoice_id", "region_code", "amount_minor"],
947
+ "columns": [
948
+ "invoice_id",
949
+ "region_code",
950
+ "amount_minor"
951
+ ],
319
952
  "where": [
320
- { "column": "status", "op": "eq", "value": "PAID" },
321
- { "column": "invoice_date", "op": "gte", "value": "2026-02-01" },
322
- { "column": "currency", "op": "in", "value": ["USD", "EUR"] },
323
- { "column": "closed_at", "op": "isNull" }
953
+ {
954
+ "column": "status",
955
+ "op": "eq",
956
+ "value": "PAID"
957
+ },
958
+ {
959
+ "column": "invoice_date",
960
+ "op": "gte",
961
+ "value": "2026-02-01"
962
+ },
963
+ {
964
+ "column": "currency",
965
+ "op": "in",
966
+ "value": [
967
+ "USD",
968
+ "EUR"
969
+ ]
970
+ },
971
+ {
972
+ "column": "closed_at",
973
+ "op": "isNull"
974
+ }
324
975
  ],
325
976
  "limit": 500
326
977
  }
@@ -330,13 +981,34 @@
330
981
  "operation": "warehouse.view.read",
331
982
  "resource": "invoices",
332
983
  "input": {
333
- "where": [{ "column": "status", "op": "eq", "value": "PAID" }],
334
- "groupBy": ["region_code"],
984
+ "where": [
985
+ {
986
+ "column": "status",
987
+ "op": "eq",
988
+ "value": "PAID"
989
+ }
990
+ ],
991
+ "groupBy": [
992
+ "region_code"
993
+ ],
335
994
  "aggregates": [
336
- { "fn": "sum", "column": "amount_minor", "as": "total_minor" },
337
- { "fn": "count", "column": "*", "as": "invoices" }
995
+ {
996
+ "fn": "sum",
997
+ "column": "amount_minor",
998
+ "as": "total_minor"
999
+ },
1000
+ {
1001
+ "fn": "count",
1002
+ "column": "*",
1003
+ "as": "invoices"
1004
+ }
1005
+ ],
1006
+ "orderBy": [
1007
+ {
1008
+ "column": "total_minor",
1009
+ "direction": "desc"
1010
+ }
338
1011
  ],
339
- "orderBy": [{ "column": "total_minor", "direction": "desc" }],
340
1012
  "limit": 100
341
1013
  }
342
1014
  },
@@ -344,42 +1016,134 @@
344
1016
  "connection": "company-slack",
345
1017
  "operation": "slack.message.update",
346
1018
  "resource": "team-updates",
347
- "input": { "messageId": "1725840100.000200", "text": "Weekly update posted *(corrected)*.", "format": "mrkdwn" },
1019
+ "input": {
1020
+ "messageId": "1725840100.000200",
1021
+ "text": "Weekly update posted *(corrected)*.",
1022
+ "format": "mrkdwn"
1023
+ },
348
1024
  "mode": "app"
349
1025
  },
350
- { "connection": "company-slack", "operation": "slack.message.delete", "resource": "team-updates", "input": { "messageId": "1725840100.000200" } },
1026
+ {
1027
+ "connection": "company-slack",
1028
+ "operation": "slack.message.delete",
1029
+ "resource": "team-updates",
1030
+ "input": {
1031
+ "messageId": "1725840100.000200"
1032
+ }
1033
+ },
351
1034
  {
352
1035
  "connection": "company-slack",
353
1036
  "operation": "slack.dm.send",
354
1037
  "resource": "workspace",
355
- "input": { "to": ["jane@example.com"], "text": "Your expense report was approved." },
1038
+ "input": {
1039
+ "to": [
1040
+ "jane@example.com"
1041
+ ],
1042
+ "text": "Your expense report was approved."
1043
+ },
356
1044
  "idempotencyKey": "6f1c2c1e-3a1c-4c3e-9f0e-2b6f7d8a9c13"
357
1045
  },
358
- { "connection": "company-slack", "operation": "slack.user.lookup", "resource": "workspace", "input": { "email": "jane@example.com" } },
1046
+ {
1047
+ "connection": "company-slack",
1048
+ "operation": "slack.user.lookup",
1049
+ "resource": "workspace",
1050
+ "input": {
1051
+ "email": "jane@example.com"
1052
+ }
1053
+ },
359
1054
  {
360
1055
  "connection": "company-slack",
361
1056
  "operation": "slack.message.post",
362
1057
  "resource": "team-updates",
363
- "input": { "text": "Thanks, shipped.", "threadId": "1725840000.000100", "format": "plain" },
1058
+ "input": {
1059
+ "text": "Thanks, shipped.",
1060
+ "threadId": "1725840000.000100",
1061
+ "format": "plain"
1062
+ },
364
1063
  "idempotencyKey": "6f1c2c1e-3a1c-4c3e-9f0e-2b6f7d8a9c14",
365
1064
  "mode": "user"
366
1065
  },
367
- { "connection": "company-slack", "operation": "slack.channel.history", "resource": "team-updates", "input": { "limit": 100, "cursor": "bmV4dF90czoxNzI1ODQwMDAwMDAwMTAw", "oldest": "1725750000.000000" }, "mode": "app" },
368
- { "connection": "gmail", "operation": "gmail.thread.list", "resource": "inbox", "input": { "limit": 50, "q": "from:jane@example.com newer_than:7d", "labelIds": ["INBOX", "IMPORTANT"] } },
369
- { "connection": "gmail", "operation": "gmail.thread.read", "resource": "inbox", "input": { "threadId": "18f2c3a4b5d6e7f0" } },
370
- { "connection": "gmail", "operation": "gmail.attachment.read", "resource": "inbox", "input": { "messageId": "18f2c3a4b5d6e7f8", "attachmentId": "ANGjdJ8w9h1Q2k3L4m5N6o7P8q9R0s" } },
1066
+ {
1067
+ "connection": "company-slack",
1068
+ "operation": "slack.channel.history",
1069
+ "resource": "team-updates",
1070
+ "input": {
1071
+ "limit": 100,
1072
+ "cursor": "bmV4dF90czoxNzI1ODQwMDAwMDAwMTAw",
1073
+ "oldest": "1725750000.000000"
1074
+ },
1075
+ "mode": "app"
1076
+ },
1077
+ {
1078
+ "connection": "gmail",
1079
+ "operation": "gmail.thread.list",
1080
+ "resource": "inbox",
1081
+ "input": {
1082
+ "limit": 50,
1083
+ "q": "from:jane@example.com newer_than:7d",
1084
+ "labelIds": [
1085
+ "INBOX",
1086
+ "IMPORTANT"
1087
+ ]
1088
+ }
1089
+ },
1090
+ {
1091
+ "connection": "gmail",
1092
+ "operation": "gmail.thread.read",
1093
+ "resource": "inbox",
1094
+ "input": {
1095
+ "threadId": "18f2c3a4b5d6e7f0"
1096
+ }
1097
+ },
1098
+ {
1099
+ "connection": "gmail",
1100
+ "operation": "gmail.attachment.read",
1101
+ "resource": "inbox",
1102
+ "input": {
1103
+ "messageId": "18f2c3a4b5d6e7f8",
1104
+ "attachmentId": "ANGjdJ8w9h1Q2k3L4m5N6o7P8q9R0s"
1105
+ }
1106
+ },
371
1107
  {
372
1108
  "connection": "gmail",
373
1109
  "operation": "gmail.message.send",
374
1110
  "resource": "inbox",
375
1111
  "input": {
376
- "to": ["jane@example.com"],
1112
+ "to": [
1113
+ "jane@example.com"
1114
+ ],
377
1115
  "subject": "Weekly report",
378
1116
  "text": "The weekly report is attached.",
379
1117
  "html": "<p>The weekly report is <b>attached</b>.</p>",
380
- "attachments": [{ "filename": "weekly-report.pdf", "contentType": "application/pdf", "dataBase64": "JVBERi0xLjQKJcOkw7zDtsOfCg==" }]
1118
+ "attachments": [
1119
+ {
1120
+ "filename": "weekly-report.pdf",
1121
+ "contentType": "application/pdf",
1122
+ "dataBase64": "JVBERi0xLjQKJcOkw7zDtsOfCg=="
1123
+ }
1124
+ ]
381
1125
  },
382
1126
  "idempotencyKey": "6f1c2c1e-3a1c-4c3e-9f0e-2b6f7d8a9c15"
1127
+ },
1128
+ {
1129
+ "connection": "inventory",
1130
+ "operation": "service.request",
1131
+ "input": {
1132
+ "method": "POST",
1133
+ "path": "orders/{id}",
1134
+ "pathParams": {
1135
+ "id": "42"
1136
+ },
1137
+ "query": {
1138
+ "notify": true
1139
+ },
1140
+ "headers": {
1141
+ "X-Trace": "test"
1142
+ },
1143
+ "body": {
1144
+ "text": "approved"
1145
+ }
1146
+ }
383
1147
  }
384
1148
  ]
385
1149
  }