@fourier-labs/harbour-contracts 0.77.0 → 0.79.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/contract-release.json +31 -25
- package/dist/gen-ts/app-integrations-declaration.d.ts +11 -2
- package/dist/gen-ts/app-sign-in.d.ts +9 -0
- package/dist/gen-ts/app-sign-in.js +3 -0
- package/dist/gen-ts/contract-release.d.ts +6 -6
- package/dist/gen-ts/contract-release.js +6 -6
- package/dist/gen-ts/data-grant-v2.d.ts +5 -5
- package/dist/gen-ts/deployment-app-registration.d.ts +8 -2
- package/dist/gen-ts/external-sign-in.d.ts +29 -0
- package/dist/gen-ts/external-sign-in.js +3 -0
- package/dist/gen-ts/identity-context.d.ts +18 -0
- package/dist/gen-ts/identity-context.js +3 -0
- package/dist/gen-ts/index.d.ts +3 -0
- package/dist/gen-ts/index.js +3 -0
- package/dist/gen-ts/integration-execute-input-rules.d.ts +202 -0
- package/dist/gen-ts/integration-execute-input-rules.js +88 -2
- package/dist/gen-ts/integration-execute-request.d.ts +52 -6
- package/dist/gen-ts/integration-execute-result.d.ts +90 -2
- package/dist/gen-ts/integration-operations.d.ts +63 -0
- package/dist/gen-ts/integration-operations.js +81 -0
- package/dist/gen-ts/saas-connection.d.ts +11 -11
- package/gen-ts/app-integrations-declaration.ts +11 -2
- package/gen-ts/app-sign-in.ts +12 -0
- package/gen-ts/contract-release.ts +6 -6
- package/gen-ts/data-grant-v2.ts +13 -4
- package/gen-ts/deployment-app-registration.ts +8 -2
- package/gen-ts/external-sign-in.ts +32 -0
- package/gen-ts/identity-context.ts +21 -0
- package/gen-ts/index.ts +3 -0
- package/gen-ts/integration-execute-input-rules.ts +90 -4
- package/gen-ts/integration-execute-request.ts +60 -5
- package/gen-ts/integration-execute-result.ts +90 -2
- package/gen-ts/integration-operations.ts +81 -0
- package/gen-ts/saas-connection.ts +19 -10
- package/package.json +1 -1
- package/schema/app-integrations-declaration.schema.json +193 -3
- package/schema/app-sign-in.schema.json +51 -0
- package/schema/data-grant-v2.schema.json +4 -4
- package/schema/deployment-app-registration.schema.json +5 -2
- package/schema/external-sign-in.schema.json +120 -0
- package/schema/identity-context.schema.json +141 -0
- package/schema/integration-execute-request.schema.json +497 -7
- package/schema/integration-execute-result.schema.json +345 -4
- package/schema/saas-connection.schema.json +27 -10
|
@@ -172,6 +172,75 @@ export const IntegrationExecuteInputRules = {
|
|
|
172
172
|
pageToken: { kind: "string", required: false, minLength: 1, maxLength: 256 },
|
|
173
173
|
},
|
|
174
174
|
},
|
|
175
|
+
"sharepoint.list.list": {
|
|
176
|
+
idempotencyKey: false,
|
|
177
|
+
fields: {
|
|
178
|
+
pageToken: { kind: "string", required: false, minLength: 1, maxLength: 2048 },
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
"sharepoint.item.list": {
|
|
182
|
+
idempotencyKey: false,
|
|
183
|
+
fields: {
|
|
184
|
+
listId: { kind: "string", required: true, minLength: 0, pattern: "^[A-Za-z0-9!._,=-]{1,256}$" },
|
|
185
|
+
filter: { kind: "string", required: false, minLength: 1, maxLength: 512 },
|
|
186
|
+
pageToken: { kind: "string", required: false, minLength: 1, maxLength: 2048 },
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
"sharepoint.item.create": {
|
|
190
|
+
idempotencyKey: true,
|
|
191
|
+
fields: {
|
|
192
|
+
listId: { kind: "string", required: true, minLength: 0, pattern: "^[A-Za-z0-9!._,=-]{1,256}$" },
|
|
193
|
+
fields: { kind: "record", required: true, minProperties: 1, maxProperties: 32, types: ["string", "number", "boolean", "null"], maxLength: 4000 },
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
"sharepoint.item.update": {
|
|
197
|
+
idempotencyKey: false,
|
|
198
|
+
fields: {
|
|
199
|
+
listId: { kind: "string", required: true, minLength: 0, pattern: "^[A-Za-z0-9!._,=-]{1,256}$" },
|
|
200
|
+
itemId: { kind: "string", required: true, minLength: 0, pattern: "^[A-Za-z0-9!._,=-]{1,256}$" },
|
|
201
|
+
fields: { kind: "record", required: true, minProperties: 1, maxProperties: 32, types: ["string", "number", "boolean", "null"], maxLength: 4000 },
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
"sharepoint.drive.list": {
|
|
205
|
+
idempotencyKey: false,
|
|
206
|
+
fields: {
|
|
207
|
+
pageToken: { kind: "string", required: false, minLength: 1, maxLength: 2048 },
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
"sharepoint.file.list": {
|
|
211
|
+
idempotencyKey: false,
|
|
212
|
+
fields: {
|
|
213
|
+
driveId: { kind: "string", required: false, minLength: 0, pattern: "^[A-Za-z0-9!._,=-]{1,256}$" },
|
|
214
|
+
folderId: { kind: "string", required: false, minLength: 0, pattern: "^[A-Za-z0-9!._,=-]{1,256}$" },
|
|
215
|
+
pageToken: { kind: "string", required: false, minLength: 1, maxLength: 2048 },
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
"sharepoint.file.search": {
|
|
219
|
+
idempotencyKey: false,
|
|
220
|
+
fields: {
|
|
221
|
+
q: { kind: "string", required: true, minLength: 1, maxLength: 256 },
|
|
222
|
+
driveId: { kind: "string", required: false, minLength: 0, pattern: "^[A-Za-z0-9!._,=-]{1,256}$" },
|
|
223
|
+
pageToken: { kind: "string", required: false, minLength: 1, maxLength: 2048 },
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
"sharepoint.file.read": {
|
|
227
|
+
idempotencyKey: false,
|
|
228
|
+
fields: {
|
|
229
|
+
fileId: { kind: "string", required: true, minLength: 0, pattern: "^[A-Za-z0-9!._,=-]{1,256}$" },
|
|
230
|
+
driveId: { kind: "string", required: false, minLength: 0, pattern: "^[A-Za-z0-9!._,=-]{1,256}$" },
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
"sharepoint.file.upload": {
|
|
234
|
+
idempotencyKey: true,
|
|
235
|
+
fields: {
|
|
236
|
+
filename: { kind: "string", required: true, minLength: 1, maxLength: 255 },
|
|
237
|
+
contentType: { kind: "string", required: true, minLength: 1, maxLength: 128 },
|
|
238
|
+
dataBase64: { kind: "string", required: true, minLength: 1, maxLength: 2796203 },
|
|
239
|
+
driveId: { kind: "string", required: false, minLength: 0, pattern: "^[A-Za-z0-9!._,=-]{1,256}$" },
|
|
240
|
+
folderId: { kind: "string", required: false, minLength: 0, pattern: "^[A-Za-z0-9!._,=-]{1,256}$" },
|
|
241
|
+
conflict: { kind: "enum", required: false, values: ["rename", "replace", "fail"], default: "rename" },
|
|
242
|
+
},
|
|
243
|
+
},
|
|
175
244
|
"warehouse.view.read": {
|
|
176
245
|
idempotencyKey: false,
|
|
177
246
|
fields: {
|
|
@@ -223,17 +292,22 @@ function matches(pattern, value) {
|
|
|
223
292
|
return compiled.test(value);
|
|
224
293
|
}
|
|
225
294
|
function refuse(field, message) { return { ok: false, problem: { field, message } }; }
|
|
226
|
-
const valueNouns = { string: ['a string', 'strings'], number: ['a number', 'numbers'], boolean: ['a boolean', 'booleans'] };
|
|
295
|
+
const valueNouns = { string: ['a string', 'strings'], number: ['a number', 'numbers'], boolean: ['a boolean', 'booleans'], null: ['null', 'nulls'] };
|
|
227
296
|
function either(parts) {
|
|
228
297
|
return parts.length > 1 ? `${parts.slice(0, -1).join(', ')} or ${parts[parts.length - 1]}` : parts.join('');
|
|
229
298
|
}
|
|
230
299
|
function isScalar(types, maxLength, value) {
|
|
300
|
+
if (value === null)
|
|
301
|
+
return types.includes('null');
|
|
231
302
|
if (typeof value === 'string')
|
|
232
303
|
return types.includes('string') && (maxLength === undefined || value.length <= maxLength);
|
|
233
304
|
if (typeof value === 'number')
|
|
234
305
|
return types.includes('number') && Number.isFinite(value);
|
|
235
306
|
return typeof value === 'boolean' && types.includes('boolean');
|
|
236
307
|
}
|
|
308
|
+
function scalarShapes(types, maxLength) {
|
|
309
|
+
return types.map(type => (type === 'string' && maxLength !== undefined ? `a string of at most ${maxLength} characters` : valueNouns[type][0]));
|
|
310
|
+
}
|
|
237
311
|
/**
|
|
238
312
|
* Checks one object's fields in rule order and applies defaults: the input
|
|
239
313
|
* itself (where null counts as absent), or one entry of an object[] input
|
|
@@ -273,6 +347,18 @@ function checkField(operation, path, name, field, value) {
|
|
|
273
347
|
if (typeof value !== 'boolean')
|
|
274
348
|
return refuse(path, `${path} must be a boolean`);
|
|
275
349
|
return { ok: true, value };
|
|
350
|
+
case 'record': {
|
|
351
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
352
|
+
return refuse(path, `${path} must be a JSON object`);
|
|
353
|
+
const keys = Object.keys(value);
|
|
354
|
+
if (keys.length < field.minProperties || keys.length > field.maxProperties)
|
|
355
|
+
return refuse(path, `${path} must have ${field.minProperties} to ${field.maxProperties} properties`);
|
|
356
|
+
for (const key of keys) {
|
|
357
|
+
if (!isScalar(field.types, field.maxLength, value[key]))
|
|
358
|
+
return refuse(`${path}.${key}`, `${path}.${key} must be ${either(scalarShapes(field.types, field.maxLength))}`);
|
|
359
|
+
}
|
|
360
|
+
return { ok: true, value };
|
|
361
|
+
}
|
|
276
362
|
case 'integer':
|
|
277
363
|
if (typeof value !== 'number' || !Number.isInteger(value) || value < field.minimum || value > field.maximum)
|
|
278
364
|
return refuse(path, `${path} must be an integer between ${field.minimum} and ${field.maximum}`);
|
|
@@ -318,7 +404,7 @@ function checkField(operation, path, name, field, value) {
|
|
|
318
404
|
const { types, maxLength, list } = field;
|
|
319
405
|
if (isScalar(types, maxLength, value) || (list !== undefined && Array.isArray(value) && value.length >= list.minItems && value.length <= list.maxItems && value.every(item => isScalar(list.types, maxLength, item))))
|
|
320
406
|
return { ok: true, value };
|
|
321
|
-
const shapes = types
|
|
407
|
+
const shapes = scalarShapes(types, maxLength);
|
|
322
408
|
if (list !== undefined)
|
|
323
409
|
shapes.push(`a list of ${list.minItems} to ${list.maxItems} ${either(list.types.map(type => valueNouns[type][1]))}`);
|
|
324
410
|
return refuse(path, `${path} must be ${either(shapes)}`);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 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). Basecamp (0.76.0, ADR 0022; every id a string of 1..20 digits, every operation user mode): basecamp.project.list {pageToken?, projectStatus? active|archived} on the fixed `account` alias; on a project alias basecamp.todolist.list {pageToken?}; basecamp.todo.list {todolistId, completed?, pageToken?}; basecamp.todo.create {todolistId, content 1..1000 (the to-do title), description? <= 16384, contentFormat? plain|html, dueOn? YYYY-MM-DD, assigneeIds? 1..20, notify?} with a required idempotencyKey; basecamp.todo.complete {todoId} (idempotent at Basecamp, so no key); basecamp.message.list {pageToken?}; basecamp.message.post {subject 1..256, content 1..65536, contentFormat?} with a required idempotencyKey; basecamp.comment.list {recordingId, pageToken?}; basecamp.comment.post {recordingId, content 1..16384, contentFormat?} with a required idempotencyKey; basecamp.campfire.post {content 1..4000, contentFormat?} with a required idempotencyKey; basecamp.people.list {pageToken?}. `pageToken` is the previous page's `nextPageToken` (a Basecamp page number as digits). 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) and a basecamp.message.post (its 64 KiB body; governance allows 80 KiB there), total deadline 10 s.
|
|
2
|
+
* 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). Basecamp (0.76.0, ADR 0022; every id a string of 1..20 digits, every operation user mode): basecamp.project.list {pageToken?, projectStatus? active|archived} on the fixed `account` alias; on a project alias basecamp.todolist.list {pageToken?}; basecamp.todo.list {todolistId, completed?, pageToken?}; basecamp.todo.create {todolistId, content 1..1000 (the to-do title), description? <= 16384, contentFormat? plain|html, dueOn? YYYY-MM-DD, assigneeIds? 1..20, notify?} with a required idempotencyKey; basecamp.todo.complete {todoId} (idempotent at Basecamp, so no key); basecamp.message.list {pageToken?}; basecamp.message.post {subject 1..256, content 1..65536, contentFormat?} with a required idempotencyKey; basecamp.comment.list {recordingId, pageToken?}; basecamp.comment.post {recordingId, content 1..16384, contentFormat?} with a required idempotencyKey; basecamp.campfire.post {content 1..4000, contentFormat?} with a required idempotencyKey; basecamp.people.list {pageToken?}. `pageToken` is the previous page's `nextPageToken` (a Basecamp page number as digits). SharePoint (0.78.0, ADR 0024; every id a Microsoft Graph id — GraphId, 1..256 characters — every operation user mode on a site alias): sharepoint.list.list {pageToken?}; sharepoint.item.list {listId, filter? 1..512 (an OData filter on `fields/...`), pageToken?}; sharepoint.item.create {listId, fields (1..32 columns, each a string <= 4000, a number, a boolean or null)} with a required idempotencyKey; sharepoint.item.update {listId, itemId, fields} (a PATCH, idempotent at Graph, so no key); sharepoint.drive.list {pageToken?}; sharepoint.file.list {driveId?, folderId?, pageToken?} (no driveId: the site's default drive; no folderId: its root); sharepoint.file.search {q 1..256, driveId?, pageToken?}; sharepoint.file.read {fileId, driveId?}; sharepoint.file.upload {filename 1..255, contentType, dataBase64 <= 2 MiB, driveId?, folderId?, conflict? rename|replace|fail} with a required idempotencyKey. A SharePoint `pageToken` is Graph's `$skiptoken` (up to 2048 characters; the shared field is that wide, the Gmail and Basecamp operations narrow it back to 256). 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) and a basecamp.message.post (its 64 KiB body; governance allows 80 KiB there) and a sharepoint.file.upload (its 2 MiB file as base64; governance sizes it like the Gmail attachment route), total deadline 10 s.
|
|
3
3
|
*/
|
|
4
4
|
export type IntegrationExecuteRequest = {
|
|
5
5
|
/**
|
|
@@ -7,9 +7,9 @@ export type IntegrationExecuteRequest = {
|
|
|
7
7
|
*/
|
|
8
8
|
connection: string;
|
|
9
9
|
/**
|
|
10
|
-
* Closed operation set. Extending it is a contracts change. 0.76.0 (ADR 0022) adds the eleven Basecamp operations, every one user mode only like gmail.*: basecamp.project.list (the projects of the company's pinned Basecamp account; its resource is the fixed `account` alias), and on one mapped project (the resource is a project alias, a key of the connection's `channels` map) basecamp.todolist.list, basecamp.todo.list, basecamp.todo.create (a receipted write: explicit action, idempotencyKey), basecamp.todo.complete (idempotent at Basecamp, no key), basecamp.message.list, basecamp.message.post, basecamp.comment.list, basecamp.comment.post, basecamp.campfire.post (the three posts are receipted writes) and basecamp.people.list. gmail.message.send (0.50.0) sends one email as the signed-in person: a send like slack.message.post (explicit Send, idempotencyKey, write receipt) under user identity; since 0.71.0 it may carry an HTML body and attachments. slack.message.post (0.53.0) runs in the mode the app declares: app (the company's Slack app, under the IT-approved per-app name) or user (the signed-in person, after their consent); since 0.55.0 each call names which of the approved modes it runs under. 0.71.0 (ADR 0018) adds slack.message.update and slack.message.delete (declared mode, like post), slack.dm.send (app mode: a direct message to 1..8 workspace members named by email), slack.user.lookup (app mode: email to Slack user id), gmail.thread.read (every message of one thread) and gmail.attachment.read (one attachment's bytes), and lets slack.channel.history run in app mode as well as user mode.
|
|
10
|
+
* Closed operation set. Extending it is a contracts change. 0.78.0 (ADR 0024) adds the nine SharePoint operations, every one user mode only on a site alias (a key of the connection's `channels` map, holding a Microsoft Graph site id): sharepoint.list.list, sharepoint.item.list, sharepoint.item.create (a receipted write: explicit action, idempotencyKey), sharepoint.item.update (a PATCH, idempotent at Graph, no key), sharepoint.drive.list, sharepoint.file.list, sharepoint.file.search, sharepoint.file.read and sharepoint.file.upload (a receipted write). 0.76.0 (ADR 0022) adds the eleven Basecamp operations, every one user mode only like gmail.*: basecamp.project.list (the projects of the company's pinned Basecamp account; its resource is the fixed `account` alias), and on one mapped project (the resource is a project alias, a key of the connection's `channels` map) basecamp.todolist.list, basecamp.todo.list, basecamp.todo.create (a receipted write: explicit action, idempotencyKey), basecamp.todo.complete (idempotent at Basecamp, no key), basecamp.message.list, basecamp.message.post, basecamp.comment.list, basecamp.comment.post, basecamp.campfire.post (the three posts are receipted writes) and basecamp.people.list. gmail.message.send (0.50.0) sends one email as the signed-in person: a send like slack.message.post (explicit Send, idempotencyKey, write receipt) under user identity; since 0.71.0 it may carry an HTML body and attachments. slack.message.post (0.53.0) runs in the mode the app declares: app (the company's Slack app, under the IT-approved per-app name) or user (the signed-in person, after their consent); since 0.55.0 each call names which of the approved modes it runs under. 0.71.0 (ADR 0018) adds slack.message.update and slack.message.delete (declared mode, like post), slack.dm.send (app mode: a direct message to 1..8 workspace members named by email), slack.user.lookup (app mode: email to Slack user id), gmail.thread.read (every message of one thread) and gmail.attachment.read (one attachment's bytes), and lets slack.channel.history run in app mode as well as user mode.
|
|
11
11
|
*/
|
|
12
|
-
operation: 'slack.channel.history' | 'slack.message.post' | 'slack.message.update' | 'slack.message.delete' | 'slack.dm.send' | 'slack.user.lookup' | 'gmail.thread.list' | 'gmail.thread.read' | 'gmail.message.read' | 'gmail.message.send' | 'gmail.attachment.read' | 'basecamp.project.list' | 'basecamp.todolist.list' | 'basecamp.todo.list' | 'basecamp.todo.create' | 'basecamp.todo.complete' | 'basecamp.message.list' | 'basecamp.message.post' | 'basecamp.comment.list' | 'basecamp.comment.post' | 'basecamp.campfire.post' | 'basecamp.people.list' | 'warehouse.view.read' | 'service.request';
|
|
12
|
+
operation: 'slack.channel.history' | 'slack.message.post' | 'slack.message.update' | 'slack.message.delete' | 'slack.dm.send' | 'slack.user.lookup' | 'gmail.thread.list' | 'gmail.thread.read' | 'gmail.message.read' | 'gmail.message.send' | 'gmail.attachment.read' | 'basecamp.project.list' | 'basecamp.todolist.list' | 'basecamp.todo.list' | 'basecamp.todo.create' | 'basecamp.todo.complete' | 'basecamp.message.list' | 'basecamp.message.post' | 'basecamp.comment.list' | 'basecamp.comment.post' | 'basecamp.campfire.post' | 'basecamp.people.list' | 'sharepoint.list.list' | 'sharepoint.item.list' | 'sharepoint.item.create' | 'sharepoint.item.update' | 'sharepoint.drive.list' | 'sharepoint.file.list' | 'sharepoint.file.search' | 'sharepoint.file.read' | 'sharepoint.file.upload' | 'warehouse.view.read' | 'service.request';
|
|
13
13
|
/**
|
|
14
14
|
* Tenant-wide logical alias of a connection (governance sourceId) or of a resource under it.
|
|
15
15
|
*/
|
|
@@ -52,7 +52,7 @@ export type IntegrationExecuteRequest = {
|
|
|
52
52
|
*/
|
|
53
53
|
latest?: string;
|
|
54
54
|
/**
|
|
55
|
-
* gmail.thread.list (0.71.0): a Gmail search query (the search-box syntax, e.g. `from:jane newer_than:7d`).
|
|
55
|
+
* gmail.thread.list (0.71.0): a Gmail search query (the search-box syntax, e.g. `from:jane newer_than:7d`). sharepoint.file.search (0.78.0): the words to search a drive's file names and contents for.
|
|
56
56
|
*/
|
|
57
57
|
q?: string;
|
|
58
58
|
/**
|
|
@@ -63,7 +63,7 @@ export type IntegrationExecuteRequest = {
|
|
|
63
63
|
*/
|
|
64
64
|
labelIds?: string[];
|
|
65
65
|
/**
|
|
66
|
-
* gmail.thread.list (0.71.0)
|
|
66
|
+
* gmail.thread.list (0.71.0), every basecamp.*.list operation (0.76.0) and the paged sharepoint.* reads (0.78.0: list.list, item.list, drive.list, file.list, file.search): the `nextPageToken` of the previous page; absent reads the first page. At most 2048 characters for a Graph `$skiptoken`; the Gmail and Basecamp operations narrow it to 256.
|
|
67
67
|
*/
|
|
68
68
|
pageToken?: string;
|
|
69
69
|
/**
|
|
@@ -139,6 +139,52 @@ export type IntegrationExecuteRequest = {
|
|
|
139
139
|
* basecamp.project.list (0.76.0): which projects to list; active when absent.
|
|
140
140
|
*/
|
|
141
141
|
projectStatus?: 'active' | 'archived';
|
|
142
|
+
/**
|
|
143
|
+
* sharepoint.item.list / sharepoint.item.create / sharepoint.item.update (0.78.0): the SharePoint list, as a sharepoint.list.list result named it.
|
|
144
|
+
*/
|
|
145
|
+
listId?: string;
|
|
146
|
+
/**
|
|
147
|
+
* sharepoint.item.update (0.78.0): the list item, as a sharepoint.item.list or sharepoint.item.create result named it.
|
|
148
|
+
*/
|
|
149
|
+
itemId?: string;
|
|
150
|
+
/**
|
|
151
|
+
* sharepoint.file.list / sharepoint.file.search / sharepoint.file.read / sharepoint.file.upload (0.78.0): the document library, as a sharepoint.drive.list result named it; absent means the site's default drive (its Documents library).
|
|
152
|
+
*/
|
|
153
|
+
driveId?: string;
|
|
154
|
+
/**
|
|
155
|
+
* sharepoint.file.list / sharepoint.file.upload (0.78.0): the folder to list or upload into, as a sharepoint.file.list result named it (an entry with isFolder); absent means the drive's root.
|
|
156
|
+
*/
|
|
157
|
+
folderId?: string;
|
|
158
|
+
/**
|
|
159
|
+
* sharepoint.file.read (0.78.0): the file, as a sharepoint.file.list, sharepoint.file.search or sharepoint.file.upload result named it.
|
|
160
|
+
*/
|
|
161
|
+
fileId?: string;
|
|
162
|
+
/**
|
|
163
|
+
* sharepoint.item.create / sharepoint.item.update (0.78.0): the item's column values keyed by the column's internal name (`Title`, `Status`, `DueDate`...), 1..32 of them; each a string of at most 4000 characters, a number, a boolean or null (which clears the column on an update). Which columns the list has, and whether a value fits its column type, is Graph's check.
|
|
164
|
+
*/
|
|
165
|
+
fields?: {
|
|
166
|
+
[k: string]: string | number | boolean | null;
|
|
167
|
+
};
|
|
168
|
+
/**
|
|
169
|
+
* sharepoint.item.list (0.78.0): an OData `$filter` over the item's columns as `fields/<column> <op> <value>`, e.g. `fields/Status eq 'Open'`; absent lists every item. A filter on a column SharePoint has not indexed may be refused by Graph on a large list.
|
|
170
|
+
*/
|
|
171
|
+
filter?: string;
|
|
172
|
+
/**
|
|
173
|
+
* sharepoint.file.upload (0.78.0): what happens when a file of that name already exists in the folder; rename when absent.
|
|
174
|
+
*/
|
|
175
|
+
conflict?: 'rename' | 'replace' | 'fail';
|
|
176
|
+
/**
|
|
177
|
+
* sharepoint.file.upload (0.78.0): the name the file is stored under (the result's `filename` is the stored name, which `conflict` rename may have changed).
|
|
178
|
+
*/
|
|
179
|
+
filename?: string;
|
|
180
|
+
/**
|
|
181
|
+
* sharepoint.file.upload (0.78.0): the file's MIME type, e.g. application/pdf.
|
|
182
|
+
*/
|
|
183
|
+
contentType?: string;
|
|
184
|
+
/**
|
|
185
|
+
* sharepoint.file.upload (0.78.0): the file bytes, standard base64, at most 2 MiB of bytes (2,796,203 base64 characters) — the Gmail attachment bound.
|
|
186
|
+
*/
|
|
187
|
+
dataBase64?: string;
|
|
142
188
|
/**
|
|
143
189
|
* 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.
|
|
144
190
|
*
|
|
@@ -190,7 +236,7 @@ export type IntegrationExecuteRequest = {
|
|
|
190
236
|
};
|
|
191
237
|
};
|
|
192
238
|
/**
|
|
193
|
-
* UUID of one explicit Send action; required for the sends (slack.message.post, slack.dm.send, gmail.message.send)
|
|
239
|
+
* UUID of one explicit Send action; required for the sends (slack.message.post, slack.dm.send, gmail.message.send), the receipted Basecamp writes (basecamp.todo.create, basecamp.message.post, basecamp.comment.post, basecamp.campfire.post; 0.76.0) and the receipted SharePoint writes (sharepoint.item.create, sharepoint.file.upload; 0.78.0), refused everywhere else. The same key with the same payload and subject returns the recorded outcome for seven days.
|
|
194
240
|
*/
|
|
195
241
|
idempotencyKey?: string;
|
|
196
242
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The `data` member of a successful execute response `{data, requestId}`; the SDK unwraps `data`. Exactly one of the operation results (
|
|
2
|
+
* The `data` member of a successful execute response `{data, requestId}`; the SDK unwraps `data`. Exactly one of the operation results (thirty-three operations since 0.78.0, ADR 0018, ADR 0022 and ADR 0024): slack.channel.history -> {messages, hasMore, nextCursor?} (plain text/IDs/time, plus the display name and emoji icon a per-app-named app post carries — authorName/authorIcon, 0.55.0 — never the provider payload; `nextCursor` when hasMore, to pass as the next read's `cursor`); slack.message.post, slack.message.update, slack.message.delete -> {messageId} (the Slack ts); slack.dm.send -> {messageId, channelId} (the ts and the DM channel it went to); slack.user.lookup -> {userId, displayName}; gmail.thread.list -> {threads, hasMore, nextPageToken?} (newest matching threads; each `messageId` is the thread's newest message, what gmail.message.read takes; `nextPageToken` when hasMore); gmail.thread.read -> {id, emails} (every message of the thread, oldest first, each shaped like a gmail.message.read result); gmail.message.read -> {id, threadId, subject, from, to, date, text, truncated, attachments?} (headers plus the plain text with html stripped, cut at 16 KiB with `truncated` set; `attachments` lists the files without their bytes, which gmail.attachment.read fetches by id); gmail.attachment.read -> {filename, mimeType, size, dataBase64}; gmail.message.send -> {messageId, threadId} (the Gmail ids of the sent message and its thread); warehouse.view.read -> {rows, truncated} (rows keyed by the selected columns, or for an aggregate read by the groupBy columns then the aggregate aliases; decimal/64-bit values as strings, dates as ISO strings; since 0.70.0 every server sends `truncated`, true when more rows matched than `limit` — the server reads limit+1 rows to know — so the rows are incomplete and must not be totalled or averaged; a server before 0.70.0 omitted it). Basecamp (0.76.0, ADR 0022; every id a string of digits, every page at most 100 items, `nextPageToken` only when hasMore, text cut at 16 KiB like Gmail): basecamp.project.list -> {projects, hasMore, nextPageToken?}; basecamp.todolist.list -> {todolists, hasMore, nextPageToken?}; basecamp.todo.list -> {todos, hasMore, nextPageToken?}; basecamp.message.list -> {boardMessages, hasMore, nextPageToken?} (`boardMessages`, not `messages`, which is the Slack history shape); basecamp.comment.list -> {comments, hasMore, nextPageToken?}; basecamp.people.list -> {people, hasMore, nextPageToken?}; basecamp.todo.create, basecamp.message.post, basecamp.comment.post, basecamp.campfire.post -> {id, appUrl} (the new recording and its Basecamp page); basecamp.todo.complete -> {id, completed} with completed always true. SharePoint (0.78.0, ADR 0024; every id a Microsoft Graph id, `nextPageToken` only when hasMore and up to 2048 characters for a Graph `$skiptoken`): sharepoint.list.list -> {lists, hasMore, nextPageToken?} (at most 100, hidden lists dropped); sharepoint.item.list -> {items, hasMore, nextPageToken?} (at most 50; each item's `fields` are the column values as Graph returns them with the system columns dropped, the whole object cut at 16 KiB); sharepoint.drive.list -> {drives, hasMore, nextPageToken?}; sharepoint.file.list and sharepoint.file.search -> {files, hasMore, nextPageToken?} (at most 100 entries, folders included with isFolder); sharepoint.file.read -> {id, filename, mimeType, size, dataBase64} (the Gmail attachment shape plus the file's id; at most 2 MiB of bytes); sharepoint.item.create -> {id, webUrl} (the new item and its SharePoint page); sharepoint.item.update -> {id}; sharepoint.file.upload -> {id, filename, webUrl} (the stored file, under the name `conflict` rename may have given it). An unknown send outcome is never a result; it is an error with details.code SEND_OUTCOME_UNKNOWN. A whole result over 1 MiB is rejected, never cut down to fit — except a gmail.attachment.read or a sharepoint.file.read, whose single file is bounded by its own 2 MiB.
|
|
3
3
|
*/
|
|
4
4
|
export type IntegrationExecuteResult = {
|
|
5
5
|
/**
|
|
@@ -29,9 +29,12 @@ export type IntegrationExecuteResult = {
|
|
|
29
29
|
*/
|
|
30
30
|
threads?: GmailThread[];
|
|
31
31
|
/**
|
|
32
|
-
* gmail.thread.list (0.71.0): the token of the next page, present only when hasMore.
|
|
32
|
+
* gmail.thread.list (0.71.0), the basecamp.* lists (0.76.0) and the paged sharepoint.* reads (0.78.0): the token of the next page, present only when hasMore; up to 2048 characters since 0.78.0 for a Graph $skiptoken.
|
|
33
33
|
*/
|
|
34
34
|
nextPageToken?: string;
|
|
35
|
+
/**
|
|
36
|
+
* gmail.thread.read / gmail.message.read: the Gmail id; the Basecamp writes and complete (0.76.0): the recording's id; the SharePoint writes and sharepoint.file.read (0.78.0): the Graph id of the item or file (up to 256 characters since 0.78.0, the GraphId bound).
|
|
37
|
+
*/
|
|
35
38
|
id?: string;
|
|
36
39
|
threadId?: string;
|
|
37
40
|
subject?: string;
|
|
@@ -119,6 +122,34 @@ export type IntegrationExecuteResult = {
|
|
|
119
122
|
* basecamp.todo.complete (0.76.0): always true; the to-do is complete whether this call or an earlier one completed it.
|
|
120
123
|
*/
|
|
121
124
|
completed?: boolean;
|
|
125
|
+
/**
|
|
126
|
+
* sharepoint.list.list (0.78.0): one page of the site's lists the signed-in person can see, hidden lists dropped.
|
|
127
|
+
*
|
|
128
|
+
* @maxItems 100
|
|
129
|
+
*/
|
|
130
|
+
lists?: SharePointList[];
|
|
131
|
+
/**
|
|
132
|
+
* sharepoint.item.list (0.78.0): one page of the list's items (Graph pages of 50), filtered when the read said `filter`.
|
|
133
|
+
*
|
|
134
|
+
* @maxItems 50
|
|
135
|
+
*/
|
|
136
|
+
items?: SharePointListItem[];
|
|
137
|
+
/**
|
|
138
|
+
* sharepoint.drive.list (0.78.0): one page of the site's document libraries.
|
|
139
|
+
*
|
|
140
|
+
* @maxItems 100
|
|
141
|
+
*/
|
|
142
|
+
drives?: SharePointDrive[];
|
|
143
|
+
/**
|
|
144
|
+
* sharepoint.file.list / sharepoint.file.search (0.78.0): one page of a folder's entries (files and folders) or of the files matching the search.
|
|
145
|
+
*
|
|
146
|
+
* @maxItems 100
|
|
147
|
+
*/
|
|
148
|
+
files?: SharePointFile[];
|
|
149
|
+
/**
|
|
150
|
+
* sharepoint.item.create / sharepoint.file.upload (0.78.0): the SharePoint page of the new item or file, for a person to open.
|
|
151
|
+
*/
|
|
152
|
+
webUrl?: string;
|
|
122
153
|
status?: number;
|
|
123
154
|
headers?: {
|
|
124
155
|
[k: string]: string;
|
|
@@ -292,3 +323,60 @@ export interface BasecampPerson {
|
|
|
292
323
|
*/
|
|
293
324
|
admin: boolean;
|
|
294
325
|
}
|
|
326
|
+
/**
|
|
327
|
+
* One list of a sharepoint.list.list result (0.78.0); `id` is what sharepoint.item.list, sharepoint.item.create and sharepoint.item.update take as `listId`. `template` is the list template Graph reports (genericList, documentLibrary, tasks, events...) when it does.
|
|
328
|
+
*/
|
|
329
|
+
export interface SharePointList {
|
|
330
|
+
/**
|
|
331
|
+
* A Microsoft Graph id (0.78.0): a SharePoint list (a GUID), list item (digits), drive (`b!...`) or drive item, as Graph names it — opaque, 1..256 characters of letters, digits and `!._,=-`. How every sharepoint.* result names one and every sharepoint.* input takes one back.
|
|
332
|
+
*/
|
|
333
|
+
id: string;
|
|
334
|
+
name: string;
|
|
335
|
+
description?: string;
|
|
336
|
+
webUrl: string;
|
|
337
|
+
template?: string;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* One item of a sharepoint.item.list result (0.78.0): `fields` are its column values keyed by internal column name, as Graph returns them with the system columns (@odata.etag, ContentType, Attachments, Edit, LinkTitle*, ItemChildCount, FolderChildCount, _UIVersionString, _ComplianceFlags...) dropped and the whole object cut at 16 KiB; `id` is what sharepoint.item.update takes as `itemId`.
|
|
341
|
+
*/
|
|
342
|
+
export interface SharePointListItem {
|
|
343
|
+
/**
|
|
344
|
+
* A Microsoft Graph id (0.78.0): a SharePoint list (a GUID), list item (digits), drive (`b!...`) or drive item, as Graph names it — opaque, 1..256 characters of letters, digits and `!._,=-`. How every sharepoint.* result names one and every sharepoint.* input takes one back.
|
|
345
|
+
*/
|
|
346
|
+
id: string;
|
|
347
|
+
/**
|
|
348
|
+
* Column values as Graph returns them (strings, numbers, booleans, and lookup or person values as Graph shapes them).
|
|
349
|
+
*/
|
|
350
|
+
fields: {
|
|
351
|
+
[k: string]: unknown;
|
|
352
|
+
};
|
|
353
|
+
webUrl: string;
|
|
354
|
+
createdAt: string;
|
|
355
|
+
modifiedAt: string;
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* One document library of a sharepoint.drive.list result (0.78.0); `id` is what the sharepoint.file.* operations take as `driveId`.
|
|
359
|
+
*/
|
|
360
|
+
export interface SharePointDrive {
|
|
361
|
+
/**
|
|
362
|
+
* A Microsoft Graph id (0.78.0): a SharePoint list (a GUID), list item (digits), drive (`b!...`) or drive item, as Graph names it — opaque, 1..256 characters of letters, digits and `!._,=-`. How every sharepoint.* result names one and every sharepoint.* input takes one back.
|
|
363
|
+
*/
|
|
364
|
+
id: string;
|
|
365
|
+
name: string;
|
|
366
|
+
webUrl: string;
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* One entry of a sharepoint.file.list or sharepoint.file.search result (0.78.0): a file (`mimeType` set, `id` is what sharepoint.file.read takes as `fileId`) or a folder (`isFolder`, `id` is what sharepoint.file.list and sharepoint.file.upload take as `folderId`). `size` is in bytes; a folder's is the total of its contents.
|
|
370
|
+
*/
|
|
371
|
+
export interface SharePointFile {
|
|
372
|
+
/**
|
|
373
|
+
* A Microsoft Graph id (0.78.0): a SharePoint list (a GUID), list item (digits), drive (`b!...`) or drive item, as Graph names it — opaque, 1..256 characters of letters, digits and `!._,=-`. How every sharepoint.* result names one and every sharepoint.* input takes one back.
|
|
374
|
+
*/
|
|
375
|
+
id: string;
|
|
376
|
+
name: string;
|
|
377
|
+
isFolder: boolean;
|
|
378
|
+
size: number;
|
|
379
|
+
mimeType?: string;
|
|
380
|
+
webUrl: string;
|
|
381
|
+
modifiedAt: string;
|
|
382
|
+
}
|
|
@@ -154,6 +154,69 @@ export declare const IntegrationOperations: {
|
|
|
154
154
|
readonly declaredMode: false;
|
|
155
155
|
readonly idempotencyKey: false;
|
|
156
156
|
};
|
|
157
|
+
readonly "sharepoint.list.list": {
|
|
158
|
+
readonly kind: "saas";
|
|
159
|
+
readonly identities: readonly ["user"];
|
|
160
|
+
readonly readOnly: true;
|
|
161
|
+
readonly declaredMode: false;
|
|
162
|
+
readonly idempotencyKey: false;
|
|
163
|
+
};
|
|
164
|
+
readonly "sharepoint.item.list": {
|
|
165
|
+
readonly kind: "saas";
|
|
166
|
+
readonly identities: readonly ["user"];
|
|
167
|
+
readonly readOnly: true;
|
|
168
|
+
readonly declaredMode: false;
|
|
169
|
+
readonly idempotencyKey: false;
|
|
170
|
+
};
|
|
171
|
+
readonly "sharepoint.item.create": {
|
|
172
|
+
readonly kind: "saas";
|
|
173
|
+
readonly identities: readonly ["user"];
|
|
174
|
+
readonly readOnly: false;
|
|
175
|
+
readonly declaredMode: false;
|
|
176
|
+
readonly idempotencyKey: true;
|
|
177
|
+
};
|
|
178
|
+
readonly "sharepoint.item.update": {
|
|
179
|
+
readonly kind: "saas";
|
|
180
|
+
readonly identities: readonly ["user"];
|
|
181
|
+
readonly readOnly: false;
|
|
182
|
+
readonly declaredMode: false;
|
|
183
|
+
readonly idempotencyKey: false;
|
|
184
|
+
};
|
|
185
|
+
readonly "sharepoint.drive.list": {
|
|
186
|
+
readonly kind: "saas";
|
|
187
|
+
readonly identities: readonly ["user"];
|
|
188
|
+
readonly readOnly: true;
|
|
189
|
+
readonly declaredMode: false;
|
|
190
|
+
readonly idempotencyKey: false;
|
|
191
|
+
};
|
|
192
|
+
readonly "sharepoint.file.list": {
|
|
193
|
+
readonly kind: "saas";
|
|
194
|
+
readonly identities: readonly ["user"];
|
|
195
|
+
readonly readOnly: true;
|
|
196
|
+
readonly declaredMode: false;
|
|
197
|
+
readonly idempotencyKey: false;
|
|
198
|
+
};
|
|
199
|
+
readonly "sharepoint.file.search": {
|
|
200
|
+
readonly kind: "saas";
|
|
201
|
+
readonly identities: readonly ["user"];
|
|
202
|
+
readonly readOnly: true;
|
|
203
|
+
readonly declaredMode: false;
|
|
204
|
+
readonly idempotencyKey: false;
|
|
205
|
+
};
|
|
206
|
+
readonly "sharepoint.file.read": {
|
|
207
|
+
readonly kind: "saas";
|
|
208
|
+
readonly identities: readonly ["user"];
|
|
209
|
+
readonly readOnly: true;
|
|
210
|
+
readonly declaredMode: false;
|
|
211
|
+
readonly idempotencyKey: false;
|
|
212
|
+
};
|
|
213
|
+
readonly "sharepoint.file.upload": {
|
|
214
|
+
readonly kind: "saas";
|
|
215
|
+
readonly identities: readonly ["user"];
|
|
216
|
+
readonly readOnly: false;
|
|
217
|
+
readonly declaredMode: false;
|
|
218
|
+
readonly idempotencyKey: true;
|
|
219
|
+
};
|
|
157
220
|
readonly "warehouse.view.read": {
|
|
158
221
|
readonly kind: "database";
|
|
159
222
|
readonly identities: readonly ["app"];
|
|
@@ -204,6 +204,87 @@ export const IntegrationOperations = {
|
|
|
204
204
|
"declaredMode": false,
|
|
205
205
|
"idempotencyKey": false
|
|
206
206
|
},
|
|
207
|
+
"sharepoint.list.list": {
|
|
208
|
+
"kind": "saas",
|
|
209
|
+
"identities": [
|
|
210
|
+
"user"
|
|
211
|
+
],
|
|
212
|
+
"readOnly": true,
|
|
213
|
+
"declaredMode": false,
|
|
214
|
+
"idempotencyKey": false
|
|
215
|
+
},
|
|
216
|
+
"sharepoint.item.list": {
|
|
217
|
+
"kind": "saas",
|
|
218
|
+
"identities": [
|
|
219
|
+
"user"
|
|
220
|
+
],
|
|
221
|
+
"readOnly": true,
|
|
222
|
+
"declaredMode": false,
|
|
223
|
+
"idempotencyKey": false
|
|
224
|
+
},
|
|
225
|
+
"sharepoint.item.create": {
|
|
226
|
+
"kind": "saas",
|
|
227
|
+
"identities": [
|
|
228
|
+
"user"
|
|
229
|
+
],
|
|
230
|
+
"readOnly": false,
|
|
231
|
+
"declaredMode": false,
|
|
232
|
+
"idempotencyKey": true
|
|
233
|
+
},
|
|
234
|
+
"sharepoint.item.update": {
|
|
235
|
+
"kind": "saas",
|
|
236
|
+
"identities": [
|
|
237
|
+
"user"
|
|
238
|
+
],
|
|
239
|
+
"readOnly": false,
|
|
240
|
+
"declaredMode": false,
|
|
241
|
+
"idempotencyKey": false
|
|
242
|
+
},
|
|
243
|
+
"sharepoint.drive.list": {
|
|
244
|
+
"kind": "saas",
|
|
245
|
+
"identities": [
|
|
246
|
+
"user"
|
|
247
|
+
],
|
|
248
|
+
"readOnly": true,
|
|
249
|
+
"declaredMode": false,
|
|
250
|
+
"idempotencyKey": false
|
|
251
|
+
},
|
|
252
|
+
"sharepoint.file.list": {
|
|
253
|
+
"kind": "saas",
|
|
254
|
+
"identities": [
|
|
255
|
+
"user"
|
|
256
|
+
],
|
|
257
|
+
"readOnly": true,
|
|
258
|
+
"declaredMode": false,
|
|
259
|
+
"idempotencyKey": false
|
|
260
|
+
},
|
|
261
|
+
"sharepoint.file.search": {
|
|
262
|
+
"kind": "saas",
|
|
263
|
+
"identities": [
|
|
264
|
+
"user"
|
|
265
|
+
],
|
|
266
|
+
"readOnly": true,
|
|
267
|
+
"declaredMode": false,
|
|
268
|
+
"idempotencyKey": false
|
|
269
|
+
},
|
|
270
|
+
"sharepoint.file.read": {
|
|
271
|
+
"kind": "saas",
|
|
272
|
+
"identities": [
|
|
273
|
+
"user"
|
|
274
|
+
],
|
|
275
|
+
"readOnly": true,
|
|
276
|
+
"declaredMode": false,
|
|
277
|
+
"idempotencyKey": false
|
|
278
|
+
},
|
|
279
|
+
"sharepoint.file.upload": {
|
|
280
|
+
"kind": "saas",
|
|
281
|
+
"identities": [
|
|
282
|
+
"user"
|
|
283
|
+
],
|
|
284
|
+
"readOnly": false,
|
|
285
|
+
"declaredMode": false,
|
|
286
|
+
"idempotencyKey": true
|
|
287
|
+
},
|
|
207
288
|
"warehouse.view.read": {
|
|
208
289
|
"kind": "database",
|
|
209
290
|
"identities": [
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Slack carries an installation and a channel map; Gmail is user-consent only; Basecamp is user-consent only with an IT-pinned account and a project map in `channels`.
|
|
2
|
+
* Slack carries an installation and a channel map; Gmail is user-consent only; Basecamp is user-consent only with an IT-pinned account and a project map in `channels`; SharePoint (0.78.0) is user-consent only, pinned to the company's Microsoft 365 tenant in `account`, with a site map in `channels`.
|
|
3
3
|
*/
|
|
4
|
-
export type SaasProvider = 'slack' | 'gmail' | 'basecamp';
|
|
4
|
+
export type SaasProvider = 'slack' | 'gmail' | 'basecamp' | 'sharepoint';
|
|
5
5
|
/**
|
|
6
6
|
* DISABLED stops every app call immediately (details.code CONNECTION_DISABLED).
|
|
7
7
|
*/
|
|
@@ -23,7 +23,7 @@ export type ExternalRecipientMode = 'block' | 'allowlist' | 'allow';
|
|
|
23
23
|
*/
|
|
24
24
|
export type InstallationName = string;
|
|
25
25
|
/**
|
|
26
|
-
* Tenant-wide SaaS connection record (control table sk SAASCONN#<sourceId>, same tenant partition as database sources; alias uniqueness is enforced across both). One connection per tenant and provider: a single bot installation of the platform-owned provider app and a single logical channel map shared by every app environment (grants, requests and user consent links stay per environment). A Gmail connection has neither installation nor channels: user consent is the only step and its one resource is the fixed `inbox` alias. A Basecamp connection (0.76.0, ADR 0022) has no installation either: IT pins the company's Basecamp `account` (chosen from the accounts IT's own Basecamp sign-in belongs to) and maps project aliases to project ids in `channels`; basecamp.project.list runs on the fixed `account` alias. Contains references, never secrets: installation tokens live under their own token records. The pre-T25 per-environment shape (`clientId`, `clientSecretRef`, `environments`, `installations`) is deprecated: the control plane normalises such a stored record on read and never writes it.
|
|
26
|
+
* Tenant-wide SaaS connection record (control table sk SAASCONN#<sourceId>, same tenant partition as database sources; alias uniqueness is enforced across both). One connection per tenant and provider: a single bot installation of the platform-owned provider app and a single logical channel map shared by every app environment (grants, requests and user consent links stay per environment). A Gmail connection has neither installation nor channels: user consent is the only step and its one resource is the fixed `inbox` alias. A Basecamp connection (0.76.0, ADR 0022) has no installation either: IT pins the company's Basecamp `account` (chosen from the accounts IT's own Basecamp sign-in belongs to) and maps project aliases to project ids in `channels`; basecamp.project.list runs on the fixed `account` alias. A SharePoint connection (0.78.0, ADR 0024) is shaped the same way: IT pins the company's Microsoft 365 tenant in `account` (the Entra tenant id and the organisation's display name, from IT's own Microsoft sign-in) and maps site aliases to Microsoft Graph site ids (`hostname,siteCollectionGuid,webGuid`) in `channels`; every sharepoint.* operation runs on a site alias. Contains references, never secrets: installation tokens live under their own token records. The pre-T25 per-environment shape (`clientId`, `clientSecretRef`, `environments`, `installations`) is deprecated: the control plane normalises such a stored record on read and never writes it.
|
|
27
27
|
*/
|
|
28
28
|
export interface SaasConnection {
|
|
29
29
|
schema: 'harbour.saas-connection/1.0';
|
|
@@ -43,21 +43,21 @@ export interface SaasConnection {
|
|
|
43
43
|
installation?: SaasInstallation;
|
|
44
44
|
account?: BasecampAccount;
|
|
45
45
|
/**
|
|
46
|
-
* Logical alias → provider resource id: a Slack channel id
|
|
46
|
+
* Logical alias → provider resource id: a Slack channel id, a Basecamp project id or a SharePoint site id. Always empty for Gmail.
|
|
47
47
|
*/
|
|
48
48
|
channels: {
|
|
49
49
|
/**
|
|
50
|
-
* A Slack channel id (public, non-archived)
|
|
50
|
+
* A Slack channel id (public, non-archived), a Basecamp project id (digits) or a SharePoint site id (0.78.0: the Microsoft Graph composite `hostname,siteCollectionGuid,webGuid`).
|
|
51
51
|
*/
|
|
52
52
|
[k: string]: string;
|
|
53
53
|
};
|
|
54
54
|
/**
|
|
55
|
-
* Operations IT allows apps to request on this connection: the provider's own SaaS operations (Slack: slack.channel.history, slack.message.post, slack.message.update, slack.message.delete, slack.dm.send, slack.user.lookup; Gmail: gmail.thread.list, gmail.thread.read, gmail.message.read, gmail.message.send, gmail.attachment.read; Basecamp (0.76.0): basecamp.project.list, basecamp.todolist.list, basecamp.todo.list, basecamp.todo.create, basecamp.todo.complete, basecamp.message.list, basecamp.message.post, basecamp.comment.list, basecamp.comment.post, basecamp.campfire.post, basecamp.people.list). Never warehouse.view.read.
|
|
55
|
+
* Operations IT allows apps to request on this connection: the provider's own SaaS operations (Slack: slack.channel.history, slack.message.post, slack.message.update, slack.message.delete, slack.dm.send, slack.user.lookup; Gmail: gmail.thread.list, gmail.thread.read, gmail.message.read, gmail.message.send, gmail.attachment.read; Basecamp (0.76.0): basecamp.project.list, basecamp.todolist.list, basecamp.todo.list, basecamp.todo.create, basecamp.todo.complete, basecamp.message.list, basecamp.message.post, basecamp.comment.list, basecamp.comment.post, basecamp.campfire.post, basecamp.people.list; SharePoint (0.78.0): sharepoint.list.list, sharepoint.item.list, sharepoint.item.create, sharepoint.item.update, sharepoint.drive.list, sharepoint.file.list, sharepoint.file.search, sharepoint.file.read, sharepoint.file.upload). Never warehouse.view.read.
|
|
56
56
|
*
|
|
57
57
|
* @minItems 1
|
|
58
|
-
* @maxItems
|
|
58
|
+
* @maxItems 31
|
|
59
59
|
*/
|
|
60
|
-
operations: ('slack.channel.history' | 'slack.message.post' | 'slack.message.update' | 'slack.message.delete' | 'slack.dm.send' | 'slack.user.lookup' | 'gmail.thread.list' | 'gmail.thread.read' | 'gmail.message.read' | 'gmail.message.send' | 'gmail.attachment.read' | 'basecamp.project.list' | 'basecamp.todolist.list' | 'basecamp.todo.list' | 'basecamp.todo.create' | 'basecamp.todo.complete' | 'basecamp.message.list' | 'basecamp.message.post' | 'basecamp.comment.list' | 'basecamp.comment.post' | 'basecamp.campfire.post' | 'basecamp.people.list' | 'warehouse.view.read' | 'service.request')[];
|
|
60
|
+
operations: ('slack.channel.history' | 'slack.message.post' | 'slack.message.update' | 'slack.message.delete' | 'slack.dm.send' | 'slack.user.lookup' | 'gmail.thread.list' | 'gmail.thread.read' | 'gmail.message.read' | 'gmail.message.send' | 'gmail.attachment.read' | 'basecamp.project.list' | 'basecamp.todolist.list' | 'basecamp.todo.list' | 'basecamp.todo.create' | 'basecamp.todo.complete' | 'basecamp.message.list' | 'basecamp.message.post' | 'basecamp.comment.list' | 'basecamp.comment.post' | 'basecamp.campfire.post' | 'basecamp.people.list' | 'sharepoint.list.list' | 'sharepoint.item.list' | 'sharepoint.item.create' | 'sharepoint.item.update' | 'sharepoint.drive.list' | 'sharepoint.file.list' | 'sharepoint.file.search' | 'sharepoint.file.read' | 'sharepoint.file.upload' | 'warehouse.view.read' | 'service.request')[];
|
|
61
61
|
developmentPreapproval: DevelopmentPreapproval;
|
|
62
62
|
oauthClient?: SaasOauthClient;
|
|
63
63
|
userModeApproval?: UserModeApproval;
|
|
@@ -122,15 +122,15 @@ export interface SaasInstallation {
|
|
|
122
122
|
botName?: string;
|
|
123
123
|
}
|
|
124
124
|
/**
|
|
125
|
-
* The pinned
|
|
125
|
+
* The pinned provider account (0.76.0, Basecamp; 0.78.0, SharePoint — the name keeps its 0.76.0 spelling so consumers need not move): the one account, among those IT's own provider sign-in belongs to, that every user token on this connection must also belong to and that every resource id in `channels` lives in. For Basecamp the account (digits); for SharePoint the Microsoft 365 tenant (the Entra tenant GUID). Absent until IT chooses it; governance refuses every basecamp.* and sharepoint.* call until then.
|
|
126
126
|
*/
|
|
127
127
|
export interface BasecampAccount {
|
|
128
128
|
/**
|
|
129
|
-
* The Basecamp account id, the first path segment of the API base URL.
|
|
129
|
+
* The Basecamp account id (digits, the first path segment of the API base URL) or the Microsoft Entra tenant id (a GUID, the `tid` claim).
|
|
130
130
|
*/
|
|
131
131
|
id: string;
|
|
132
132
|
/**
|
|
133
|
-
* The account's name as
|
|
133
|
+
* The account's name as the provider shows it (a Basecamp account name, a Microsoft 365 organisation display name), for IT to recognise.
|
|
134
134
|
*/
|
|
135
135
|
name: string;
|
|
136
136
|
}
|