@fourier-labs/harbour-contracts 0.73.0 → 0.76.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 (33) hide show
  1. package/README.md +4 -4
  2. package/contract-release.json +26 -26
  3. package/dist/gen-ts/app-integrations-declaration.d.ts +15 -1
  4. package/dist/gen-ts/contract-release.d.ts +6 -6
  5. package/dist/gen-ts/contract-release.js +6 -6
  6. package/dist/gen-ts/data-grant-v2.d.ts +5 -5
  7. package/dist/gen-ts/data-source.d.ts +19 -2
  8. package/dist/gen-ts/integration-error-details.d.ts +2 -2
  9. package/dist/gen-ts/integration-execute-input-rules.d.ts +214 -0
  10. package/dist/gen-ts/integration-execute-input-rules.js +85 -0
  11. package/dist/gen-ts/integration-execute-request.d.ts +61 -6
  12. package/dist/gen-ts/integration-execute-result.d.ts +165 -1
  13. package/dist/gen-ts/integration-operations.d.ts +77 -0
  14. package/dist/gen-ts/integration-operations.js +99 -0
  15. package/dist/gen-ts/saas-connection.d.ts +22 -8
  16. package/gen-ts/app-integrations-declaration.ts +15 -1
  17. package/gen-ts/contract-release.ts +6 -6
  18. package/gen-ts/data-grant-v2.ts +15 -4
  19. package/gen-ts/data-source.ts +19 -2
  20. package/gen-ts/integration-error-details.ts +3 -2
  21. package/gen-ts/integration-execute-input-rules.ts +86 -1
  22. package/gen-ts/integration-execute-request.ts +71 -5
  23. package/gen-ts/integration-execute-result.ts +165 -1
  24. package/gen-ts/integration-operations.ts +99 -0
  25. package/gen-ts/saas-connection.ts +32 -7
  26. package/package.json +1 -1
  27. package/schema/app-integrations-declaration.schema.json +233 -2
  28. package/schema/data-grant-v2.schema.json +4 -4
  29. package/schema/data-source.schema.json +57 -2
  30. package/schema/integration-error-details.schema.json +3 -2
  31. package/schema/integration-execute-request.schema.json +563 -4
  32. package/schema/integration-execute-result.schema.json +558 -2
  33. package/schema/saas-connection.schema.json +35 -7
@@ -2,7 +2,7 @@
2
2
  // Code generated by tools/gen/gen.mjs from schema/*.json. DO NOT EDIT.
3
3
 
4
4
  /**
5
- * The `data` member of a successful execute response `{data, requestId}`; the SDK unwraps `data`. Exactly one of the operation results (twelve operations since 0.71.0, ADR 0018): 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). 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, whose single attachment is bounded by its own 2 MiB.
5
+ * The `data` member of a successful execute response `{data, requestId}`; the SDK unwraps `data`. Exactly one of the operation results (twenty-four operations since 0.76.0, ADR 0018 and ADR 0022): 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. 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, whose single attachment is bounded by its own 2 MiB.
6
6
  */
7
7
  export type IntegrationExecuteResult = {
8
8
  /**
@@ -46,6 +46,10 @@ export type IntegrationExecuteResult = {
46
46
  * gmail.message.read: the text was cut at 16 KiB. warehouse.view.read (0.70.0): more rows matched than the read's limit, so `rows` is incomplete.
47
47
  */
48
48
  truncated?: boolean;
49
+ /**
50
+ * warehouse.view.read (0.74.0): true when `rows` came from the app's sample rows (integration-execute-request.sampleRows) because the development read had no development copy of the source; the rows were never read from the source. Absent or false: the rows came from the source.
51
+ */
52
+ sample?: boolean;
49
53
  /**
50
54
  * gmail.message.read (0.71.0): the message's attachments, without their bytes.
51
55
  *
@@ -74,6 +78,50 @@ export type IntegrationExecuteResult = {
74
78
  rows?: {
75
79
  [k: string]: unknown;
76
80
  }[];
81
+ /**
82
+ * basecamp.project.list (0.76.0): one page of the pinned account's projects the signed-in person can see.
83
+ *
84
+ * @maxItems 100
85
+ */
86
+ projects?: BasecampProject[];
87
+ /**
88
+ * basecamp.todolist.list (0.76.0): one page of the project's to-do lists.
89
+ *
90
+ * @maxItems 100
91
+ */
92
+ todolists?: BasecampTodolist[];
93
+ /**
94
+ * basecamp.todo.list (0.76.0): one page of the list's open to-dos, or its completed ones when the read said `completed`.
95
+ *
96
+ * @maxItems 100
97
+ */
98
+ todos?: BasecampTodo[];
99
+ /**
100
+ * basecamp.message.list (0.76.0): one page of the project's message board, newest first. Named apart from `messages`, the Slack history shape.
101
+ *
102
+ * @maxItems 100
103
+ */
104
+ boardMessages?: BasecampMessage[];
105
+ /**
106
+ * basecamp.comment.list (0.76.0): one page of the recording's comments, oldest first.
107
+ *
108
+ * @maxItems 100
109
+ */
110
+ comments?: BasecampComment[];
111
+ /**
112
+ * basecamp.people.list (0.76.0): one page of the people on the project.
113
+ *
114
+ * @maxItems 100
115
+ */
116
+ people?: BasecampPerson[];
117
+ /**
118
+ * basecamp.todo.create / basecamp.message.post / basecamp.comment.post / basecamp.campfire.post (0.76.0): the Basecamp page of the new recording, for a person to open.
119
+ */
120
+ appUrl?: string;
121
+ /**
122
+ * basecamp.todo.complete (0.76.0): always true; the to-do is complete whether this call or an earlier one completed it.
123
+ */
124
+ completed?: boolean;
77
125
  status?: number;
78
126
  headers?: {
79
127
  [k: string]: string;
@@ -132,3 +180,119 @@ export interface GmailMessage {
132
180
  */
133
181
  attachments?: GmailAttachment[];
134
182
  }
183
+ /**
184
+ * One Basecamp project of a basecamp.project.list result (0.76.0). `id` is what IT maps a project alias to on the connection.
185
+ */
186
+ export interface BasecampProject {
187
+ /**
188
+ * A Basecamp id (0.76.0): an account, project, to-do list, to-do, recording or person, as a string of 1..20 digits — how every basecamp.* result names one and every basecamp.* input takes one back.
189
+ */
190
+ id: string;
191
+ name: string;
192
+ description?: string;
193
+ appUrl: string;
194
+ /**
195
+ * @maxItems 32
196
+ */
197
+ tools: BasecampTool[];
198
+ }
199
+ /**
200
+ * One enabled tool of a Basecamp project (0.76.0), from its dock: `name` is Basecamp's kind (todoset, message_board, chat, schedule, vault, questionnaire, inbox, kanban_board), `title` what the project calls it.
201
+ */
202
+ export interface BasecampTool {
203
+ /**
204
+ * A Basecamp id (0.76.0): an account, project, to-do list, to-do, recording or person, as a string of 1..20 digits — how every basecamp.* result names one and every basecamp.* input takes one back.
205
+ */
206
+ id: string;
207
+ name: string;
208
+ title: string;
209
+ }
210
+ /**
211
+ * One to-do list of a basecamp.todolist.list result (0.76.0); `id` is what basecamp.todo.list and basecamp.todo.create take as `todolistId`.
212
+ */
213
+ export interface BasecampTodolist {
214
+ /**
215
+ * A Basecamp id (0.76.0): an account, project, to-do list, to-do, recording or person, as a string of 1..20 digits — how every basecamp.* result names one and every basecamp.* input takes one back.
216
+ */
217
+ id: string;
218
+ name: string;
219
+ description?: string;
220
+ /**
221
+ * Basecamp's `completed_ratio`, e.g. `3/10`.
222
+ */
223
+ completedRatio: string;
224
+ appUrl: string;
225
+ }
226
+ /**
227
+ * One to-do of a basecamp.todo.list result (0.76.0): `content` is its title, `description` its notes as plain text (empty when none), each cut at 16 KiB.
228
+ */
229
+ export interface BasecampTodo {
230
+ /**
231
+ * A Basecamp id (0.76.0): an account, project, to-do list, to-do, recording or person, as a string of 1..20 digits — how every basecamp.* result names one and every basecamp.* input takes one back.
232
+ */
233
+ id: string;
234
+ content: string;
235
+ description: string;
236
+ completed: boolean;
237
+ dueOn?: string;
238
+ /**
239
+ * @maxItems 100
240
+ */
241
+ assignees: BasecampPersonRef[];
242
+ appUrl: string;
243
+ createdAt: string;
244
+ }
245
+ /**
246
+ * A person named on a Basecamp recording (0.76.0): an author or an assignee.
247
+ */
248
+ export interface BasecampPersonRef {
249
+ /**
250
+ * A Basecamp id (0.76.0): an account, project, to-do list, to-do, recording or person, as a string of 1..20 digits — how every basecamp.* result names one and every basecamp.* input takes one back.
251
+ */
252
+ id: string;
253
+ name: string;
254
+ }
255
+ /**
256
+ * One message-board post of a basecamp.message.list result (0.76.0); `content` is plain text cut at 16 KiB; `id` is a recordingId for basecamp.comment.list and basecamp.comment.post.
257
+ */
258
+ export interface BasecampMessage {
259
+ /**
260
+ * A Basecamp id (0.76.0): an account, project, to-do list, to-do, recording or person, as a string of 1..20 digits — how every basecamp.* result names one and every basecamp.* input takes one back.
261
+ */
262
+ id: string;
263
+ subject: string;
264
+ content: string;
265
+ author: BasecampPersonRef;
266
+ createdAt: string;
267
+ appUrl: string;
268
+ commentsCount: number;
269
+ }
270
+ /**
271
+ * One comment of a basecamp.comment.list result (0.76.0); `content` is plain text cut at 16 KiB.
272
+ */
273
+ export interface BasecampComment {
274
+ /**
275
+ * A Basecamp id (0.76.0): an account, project, to-do list, to-do, recording or person, as a string of 1..20 digits — how every basecamp.* result names one and every basecamp.* input takes one back.
276
+ */
277
+ id: string;
278
+ content: string;
279
+ author: BasecampPersonRef;
280
+ createdAt: string;
281
+ appUrl: string;
282
+ }
283
+ /**
284
+ * One person of a basecamp.people.list result (0.76.0); `id` is what basecamp.todo.create takes in `assigneeIds`. `email` and `title` when Basecamp shows them.
285
+ */
286
+ export interface BasecampPerson {
287
+ /**
288
+ * A Basecamp id (0.76.0): an account, project, to-do list, to-do, recording or person, as a string of 1..20 digits — how every basecamp.* result names one and every basecamp.* input takes one back.
289
+ */
290
+ id: string;
291
+ name: string;
292
+ email?: string;
293
+ title?: string;
294
+ /**
295
+ * Whether the person is an administrator of the Basecamp account.
296
+ */
297
+ admin: boolean;
298
+ }
@@ -106,6 +106,105 @@ export const IntegrationOperations = {
106
106
  "declaredMode": false,
107
107
  "idempotencyKey": false
108
108
  },
109
+ "basecamp.project.list": {
110
+ "kind": "saas",
111
+ "identities": [
112
+ "user"
113
+ ],
114
+ "readOnly": true,
115
+ "declaredMode": false,
116
+ "idempotencyKey": false
117
+ },
118
+ "basecamp.todolist.list": {
119
+ "kind": "saas",
120
+ "identities": [
121
+ "user"
122
+ ],
123
+ "readOnly": true,
124
+ "declaredMode": false,
125
+ "idempotencyKey": false
126
+ },
127
+ "basecamp.todo.list": {
128
+ "kind": "saas",
129
+ "identities": [
130
+ "user"
131
+ ],
132
+ "readOnly": true,
133
+ "declaredMode": false,
134
+ "idempotencyKey": false
135
+ },
136
+ "basecamp.todo.create": {
137
+ "kind": "saas",
138
+ "identities": [
139
+ "user"
140
+ ],
141
+ "readOnly": false,
142
+ "declaredMode": false,
143
+ "idempotencyKey": true
144
+ },
145
+ "basecamp.todo.complete": {
146
+ "kind": "saas",
147
+ "identities": [
148
+ "user"
149
+ ],
150
+ "readOnly": false,
151
+ "declaredMode": false,
152
+ "idempotencyKey": false
153
+ },
154
+ "basecamp.message.list": {
155
+ "kind": "saas",
156
+ "identities": [
157
+ "user"
158
+ ],
159
+ "readOnly": true,
160
+ "declaredMode": false,
161
+ "idempotencyKey": false
162
+ },
163
+ "basecamp.message.post": {
164
+ "kind": "saas",
165
+ "identities": [
166
+ "user"
167
+ ],
168
+ "readOnly": false,
169
+ "declaredMode": false,
170
+ "idempotencyKey": true
171
+ },
172
+ "basecamp.comment.list": {
173
+ "kind": "saas",
174
+ "identities": [
175
+ "user"
176
+ ],
177
+ "readOnly": true,
178
+ "declaredMode": false,
179
+ "idempotencyKey": false
180
+ },
181
+ "basecamp.comment.post": {
182
+ "kind": "saas",
183
+ "identities": [
184
+ "user"
185
+ ],
186
+ "readOnly": false,
187
+ "declaredMode": false,
188
+ "idempotencyKey": true
189
+ },
190
+ "basecamp.campfire.post": {
191
+ "kind": "saas",
192
+ "identities": [
193
+ "user"
194
+ ],
195
+ "readOnly": false,
196
+ "declaredMode": false,
197
+ "idempotencyKey": true
198
+ },
199
+ "basecamp.people.list": {
200
+ "kind": "saas",
201
+ "identities": [
202
+ "user"
203
+ ],
204
+ "readOnly": true,
205
+ "declaredMode": false,
206
+ "idempotencyKey": false
207
+ },
109
208
  "warehouse.view.read": {
110
209
  "kind": "database",
111
210
  "identities": [
@@ -2,9 +2,9 @@
2
2
  // Code generated by tools/gen/gen.mjs from schema/*.json. DO NOT EDIT.
3
3
 
4
4
  /**
5
- * Slack carries an installation and a channel map; Gmail is user-consent only.
5
+ * 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`.
6
6
  */
7
- export type SaasProvider = 'slack' | 'gmail';
7
+ export type SaasProvider = 'slack' | 'gmail' | 'basecamp';
8
8
  /**
9
9
  * DISABLED stops every app call immediately (details.code CONNECTION_DISABLED).
10
10
  */
@@ -27,7 +27,7 @@ export type ExternalRecipientMode = 'block' | 'allowlist' | 'allow';
27
27
  export type InstallationName = string;
28
28
 
29
29
  /**
30
- * 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. 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.
30
+ * 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.
31
31
  */
32
32
  export interface SaasConnection {
33
33
  schema: 'harbour.saas-connection/1.0';
@@ -45,20 +45,21 @@ export interface SaasConnection {
45
45
  */
46
46
  version: number;
47
47
  installation?: SaasInstallation;
48
+ account?: BasecampAccount;
48
49
  /**
49
- * Logical channel alias -> provider channel ID, shared by every environment. Always empty for Gmail.
50
+ * Logical alias → provider resource id: a Slack channel id or a Basecamp project id. Always empty for Gmail.
50
51
  */
51
52
  channels: {
52
53
  /**
53
- * Provider channel ID; only public, non-archived channels are valid.
54
+ * A Slack channel id (public, non-archived) or a Basecamp project id (digits).
54
55
  */
55
56
  [k: string]: string;
56
57
  };
57
58
  /**
58
- * 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). Never warehouse.view.read.
59
+ * 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.
59
60
  *
60
61
  * @minItems 1
61
- * @maxItems 11
62
+ * @maxItems 22
62
63
  */
63
64
  operations: (
64
65
  | 'slack.channel.history'
@@ -72,6 +73,17 @@ export interface SaasConnection {
72
73
  | 'gmail.message.read'
73
74
  | 'gmail.message.send'
74
75
  | 'gmail.attachment.read'
76
+ | 'basecamp.project.list'
77
+ | 'basecamp.todolist.list'
78
+ | 'basecamp.todo.list'
79
+ | 'basecamp.todo.create'
80
+ | 'basecamp.todo.complete'
81
+ | 'basecamp.message.list'
82
+ | 'basecamp.message.post'
83
+ | 'basecamp.comment.list'
84
+ | 'basecamp.comment.post'
85
+ | 'basecamp.campfire.post'
86
+ | 'basecamp.people.list'
75
87
  | 'warehouse.view.read'
76
88
  | 'service.request'
77
89
  )[];
@@ -138,6 +150,19 @@ export interface SaasInstallation {
138
150
  */
139
151
  botName?: string;
140
152
  }
153
+ /**
154
+ * The pinned Basecamp account (0.76.0, Basecamp only): the one account, among those IT's own Basecamp sign-in belongs to, that every user token on this connection must also belong to and that every project id in `channels` lives in. Absent until IT chooses it; governance refuses every basecamp.* call until then.
155
+ */
156
+ export interface BasecampAccount {
157
+ /**
158
+ * The Basecamp account id, the first path segment of the API base URL.
159
+ */
160
+ id: string;
161
+ /**
162
+ * The account's name as Basecamp shows it, for IT to recognise.
163
+ */
164
+ name: string;
165
+ }
141
166
  export interface DevelopmentPreapproval {
142
167
  /**
143
168
  * Auto-approve development requests within the allowlist.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fourier-labs/harbour-contracts",
3
- "version": "0.73.0",
3
+ "version": "0.76.0",
4
4
  "description": "Single source of truth for every data shape that crosses a boundary between Isomorph components.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,