@fourier-labs/harbour-contracts 0.104.0 → 0.106.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 CHANGED
@@ -38,7 +38,7 @@ exist here first.
38
38
  | `binding-classification-subject.schema.json` | Complete value-free binding-name classification before version allocation | AI governance §5.7 / SEC-UP-01 |
39
39
  | `sanitized-binding-evidence.schema.json` | Exact-version sanitized runtime-binding allocation receipt | AI governance §5.7 / SEC-UP-02 |
40
40
  | `ai-pricing-revision`, `ai-metering-policy`, `ai-budget-ledger-event`, `ai-trace-event-v2` | Additive metering v2 records, including media dimensions and P5 trace attribution; v1 records remain readable | AI governance §9 |
41
- | `app-integrations-declaration.schema.json` | Committed `.isomorph/integrations.json` (`isomorph.app-integrations/2.0`): closed eighty-one-operation set (Slack read/post/update/delete/DM/lookup, Gmail list/thread/read/send/attachment — ADR 0018; Basecamp project/to-do list/to-do/message/comment/people lists, to-do create and complete, message, comment and Campfire posts — ADR 0022; SharePoint list/item/drive/file lists, file search and read, item create and update, file upload — ADR 0024; phone SMS list — ADR 0028; phone number read — ADR 0029; Google Calendar, Drive, Docs and Sheets and Outlook, OneDrive, Excel and Teams reads and writes as the signed-in person — ADR 0032; warehouse view read and table append, the view mapping saying `access: read|append` — ADR 0028; service request) with fixed or declared identity modes (`gmail.message.send` declared since 0.102.0 — ADR 0045); owns the shared operation/identity/environment enums | Dev kit §4 |
41
+ | `app-integrations-declaration.schema.json` | Committed `.isomorph/integrations.json` (`isomorph.app-integrations/2.0`): closed eighty-one-operation set (Slack read/post/update/delete/DM/lookup, Gmail list/thread/read/send/attachment — ADR 0018; Basecamp project/to-do list/to-do/message/comment/people lists, to-do create and complete, message, comment and Campfire posts — ADR 0022; SharePoint list/item/drive/file lists, file search and read, item create and update, file upload — ADR 0024; phone SMS list — ADR 0028; phone number read — ADR 0029; Google Calendar, Drive, Docs and Sheets and Outlook, OneDrive, Excel and Teams reads and writes as the signed-in person — ADR 0032; warehouse view read and table append, the view mapping saying `access: read|append` — ADR 0028; service request) with fixed or declared identity modes (`gmail.message.send` declared since 0.102.0 — ADR 0045; the four Gmail reads since 0.106.0 — ADR 0049); owns the shared operation/identity/environment enums | Dev kit §4 |
42
42
  | `integration-execute-request`, `integration-execute-result`, `integration-connect-result`, `integration-error-details` | App SDK integrations wire contract on `/_harbour/integrations/*`: per-operation input bounds (a warehouse read filters, groups, aggregates and sorts in the warehouse and says when it was `truncated` — ADR 0017; Slack and Gmail reads page, posts thread, mail carries HTML and attachments — ADR 0018; a development warehouse read may carry the app's `sampleRows` and the result says `sample` — ADR 0021; Basecamp lists page by `pageToken`, ids are digit strings, writes answer `{id, appUrl}` — ADR 0022; SharePoint lists page by a Graph `$skiptoken`, ids are Graph ids, `fields` is a bounded record, writes answer `{id, webUrl}` — ADR 0024; a warehouse append takes `rows`, a bounded list of records, and a phone SMS list takes `since`/`from` — ADR 0028; a phone number read takes no input — ADR 0029; calendar events take RFC 3339 `start`/`end`, Sheets and Excel take `values`, a bounded matrix of cells, Docs take `replacements`, and the event read, document read, values and worksheet results have their own shapes — ADR 0032), the per-operation result shapes, consent states and the closed `details.code` reason set | Dev kit §4, §8 |
43
43
  | `saas-connection.schema.json`, `data-grant-v2.schema.json` | Tenant SaaS connection record (one installation and channel map per tenant and provider; IT's optional `oauthClient`, `userModeApproval`, `directMessages`, `externalRecipients` — ADR 0018; a Basecamp connection's pinned `account` and project ids in `channels` — ADR 0022; a SharePoint connection's pinned Microsoft 365 tenant in `account` and site ids in `channels` — ADR 0024; up to 20 `approvers` who may approve access requests beside the tenant admins — ADR 0026; a phone connection's E.164 numbers in `channels` — ADR 0028, which an app reads back by alias — ADR 0029; eight Google Workspace and Microsoft 365 providers, user-consent only, a Teams connection's pinned tenant and `<teamId>/<channelId>` channel map — ADR 0032; optional `startedBy` when a builder's first request created the connection — ADR 0034; a Gmail connection's `installation` is the company mailbox IT connected, `botName` its address — ADR 0045) and the additive `harbour.data-grant/2.0` grant record with structured resources and provisioning lease/cleanup (status machine generated; legacy `data-grant` untouched) | Dev kit §5 |
44
44
  | `kit-bundle-manifest.schema.json`, `kit-lock.schema.json` | Published kit bundle identity (SDK tarball digest plus optional `sdk.version` and https `sdk.url` download location) and the committed `.isomorph/kit.lock.json` the CodeBuild SDK-vendoring gate compares against | Dev kit §3 |
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "schema": "harbour.contract-release/1.0",
3
- "repositoryVersion": "0.104.0.0",
4
- "publishVersion": "0.104.0",
5
- "publishTag": "v0.104.0",
6
- "schemasDigest": "sha256:e5912b264382f93a4a505d7d4d447c26909e5faae4f8ac9917ce6f351ee2c767",
3
+ "repositoryVersion": "0.106.0.0",
4
+ "publishVersion": "0.106.0",
5
+ "publishTag": "v0.106.0",
6
+ "schemasDigest": "sha256:af39e976761a7b4b7b7496b97562c1c0d75ddf4c1dae316abbeec1f96c261351",
7
7
  "aiFixturesDigest": "sha256:edac31c92e146ca1c40130f2fb5649db82e24b8cd1ac758153b90612052fddb6",
8
- "generatedGoDigest": "sha256:34ea126e68d00e09b3a61d3b65d699b451e8840d3ea1c40f4abd5b066fc5f1a4",
9
- "generatedTypeScriptDigest": "sha256:386ffce01eaf19ecd3ab800edf41c3288b672acf935b2a468be1edccc7980c7e",
8
+ "generatedGoDigest": "sha256:2e8b6f09caa663a5c29f7501b0e2e70fbc6ece8918074fbb76640e6727a48f15",
9
+ "generatedTypeScriptDigest": "sha256:e6f70790135277ed4586cbc14d411c08cb6fd6cc40b25343f4ee79eb4981ec6a",
10
10
  "generatedExclusions": {
11
11
  "gen-go/contract_release.go": "contains the generated aggregate digests (self-reference)",
12
12
  "gen-ts/contract-release.ts": "contains the generated aggregate digests (self-reference)"
@@ -31,7 +31,7 @@
31
31
  "schema/ai-transform-result.schema.json": "sha256:244e380a36957ab2dcb526ea22aaecb56b53664ce1fda6934d0021c084c99133",
32
32
  "schema/ai-verification-evidence.schema.json": "sha256:bdbc762df256d2a8dd0bdda60d4d13915ce40034cd756af9515871d145f10ad9",
33
33
  "schema/api-error-response.schema.json": "sha256:3d43d5bdf0b59c944e20509d832e6776c3ecdba4d6c245a9118bbe52062a8304",
34
- "schema/app-integrations-declaration.schema.json": "sha256:2fab400674c79e53279d68cfa97a0bf8dc70d3379d4a0197ca945b4bb446b85b",
34
+ "schema/app-integrations-declaration.schema.json": "sha256:4d41c6720527f383b975a0b5d041160a1e734d8f4106fd9eb88b10071eef08eb",
35
35
  "schema/app-sign-in.schema.json": "sha256:2093740131b84a69757c2e474d16e32ff5d0bf0edd6f6f8de62ede7f82988f29",
36
36
  "schema/artifact-contract.schema.json": "sha256:8047b461a1c6d07bbe867a259c57bdf5cc0fdf282cf842ec7b683a9e93713b10",
37
37
  "schema/binding-classification-subject.schema.json": "sha256:c76c0232f0377d3b86162222d5789ac64c357899ea9f722d889b138593967af3",
@@ -42,7 +42,7 @@
42
42
  "schema/data-source.schema.json": "sha256:f32b2b4312bc06e023f4e1da60eed52ddfade59b74c1a1cc8e4b740a6f9c930b",
43
43
  "schema/deploy-request.schema.json": "sha256:c978406c273fa430db262888f0a8646b14e3057f64b0633780fcd6cfa646bc67",
44
44
  "schema/deploy-response.schema.json": "sha256:09064948abdbba7022738285d802ed44648a16ddc2a5b8c2eeb170ae19a66aeb",
45
- "schema/deployment-app-registration.schema.json": "sha256:a70887007087508209da8682f1afc9eb85d916cdc239d62c090940ffb3b56c3f",
45
+ "schema/deployment-app-registration.schema.json": "sha256:e2ad36d5cb33fd3f1f5bb04949ba2178a45008adb4bf79687f60acabe5507ba1",
46
46
  "schema/deployment-continuation.schema.json": "sha256:55b6945439f2c91cd851dfe4bc9484e67a64091b7763b2d8267354b4a8bfae8c",
47
47
  "schema/deployment-environment.schema.json": "sha256:35b26cae07da9ee375a842b5e87c4570c8eca06775e9fd6dd46eb9eb8aa0df6c",
48
48
  "schema/deployment-preview-retirement.schema.json": "sha256:ccf6b971b7853ca98530272f280fe62cd3aab5425b10b81ae6b717cc949e014f",
@@ -58,7 +58,7 @@
58
58
  "schema/identity-context.schema.json": "sha256:a0be3d3499b70f1641887ec66ab5b21ca5ab71063e9209290905738943a36e59",
59
59
  "schema/integration-connect-result.schema.json": "sha256:3f254b67ef739dc1fa395bdc8c4dc93359b16d481dba2a2d777aa5b6f34b7af8",
60
60
  "schema/integration-error-details.schema.json": "sha256:dd997865b6061c37e298a0ad8e7bcee6186422e04e3ccf63d24d64ef860036d3",
61
- "schema/integration-execute-request.schema.json": "sha256:2f1c388e90b87ecc33a28f7e9b3474f563b5a3cd495af9c45bbe7664c6c62538",
61
+ "schema/integration-execute-request.schema.json": "sha256:98209cc24339b044363c440ecfaa88b0b4820a3b4827307fb60c7f666e16d113",
62
62
  "schema/integration-execute-result.schema.json": "sha256:057b787c28b5f7339a95184fafa5deb7ce2c6441254ad8423ce3238c7dd6996c",
63
63
  "schema/internal-configuration-request.schema.json": "sha256:229d44a3458f68dceddfba802e5c1adcd95523a45af29b93b972706b88461a7d",
64
64
  "schema/kit-bundle-manifest.schema.json": "sha256:d8fc6ca9c30d791e785b021687e084e9171c0ea1a3a176d0f1582fcccf2c0e19",
@@ -114,11 +114,11 @@
114
114
  "gen-go/contracts_test.go": "sha256:56376287cb1548be1b35ab4c65d6f0b558c5d34885f7a47cb3d9e8d786607bf5",
115
115
  "gen-go/doc.go": "sha256:316d610ce568597a2ce6f38e9782e34fa5920f6f51ab0866e6d061b8a41cd822",
116
116
  "gen-go/error_category.go": "sha256:b2ddea22dcfa030f1a45e721e635485b0bd816cf1f38e7654628a653f5ed4583",
117
- "gen-go/integration_execute_input_rules.go": "sha256:1897aa9906f7d1e642df904b3a72d74fc77869eb56633dcb6303232733070c15",
118
- "gen-go/integration_execute_input_rules_test.go": "sha256:0b04eb79d07764796e72fc9f3407f5852d377e636af96f6cc5ee34e2691113ff",
119
- "gen-go/integration_operations.go": "sha256:a60ac73a7860b1b330b022ddde0c82e7ef146cc40ad22821013c8bdb73f9db28",
117
+ "gen-go/integration_execute_input_rules.go": "sha256:fb789d2875e701d5b3c57fc7f4c6a2df46beb6daa1faf4ccd1f1d4b8bb1dbe2a",
118
+ "gen-go/integration_execute_input_rules_test.go": "sha256:9d4b541fbbd8ba08a18af627ac0fae035b6bdf72d78885687fc99bff5e9cde01",
119
+ "gen-go/integration_operations.go": "sha256:bfefe75a7721a7e695217db672cfb4ba8783ce94cd621c7d8d0726b88bb8f3fc",
120
120
  "gen-go/schema_documents.go": "sha256:c19c0d540f602ee997a84b90e20702d9bc63ed7d27aec267e76c04655fc182e0",
121
- "gen-go/types.go": "sha256:d0f941cbca1772c1e17805387e0ba177d697727ec190d21f5ef83ac03a114a5f",
121
+ "gen-go/types.go": "sha256:0a5c9a6cf1618cb79911d36a8b5f6ac291b3e849e91a247b1d927d6b34205563",
122
122
  "gen-go/version_state.go": "sha256:4ef8abb126414c0025cf2c59bc9c79a74ef4a2ff89e8f316cbf05acc872371c1"
123
123
  },
124
124
  "generatedTypeScript": {
@@ -143,7 +143,7 @@
143
143
  "gen-ts/ai-transform-result.ts": "sha256:208c66b2aeb425c0cbcc18b7191f6e8989443f2b2c1690648e0e06cc1448ce25",
144
144
  "gen-ts/ai-verification-evidence.ts": "sha256:979439d77f8b47631d1eb3b6c3c54ea6fec41089d2a51dc815fd370b1c4917fc",
145
145
  "gen-ts/api-error-response.ts": "sha256:0fd73be282e7a5d72d6c343a4e64d09c232556b16161167f0cffd6278ed7e3d3",
146
- "gen-ts/app-integrations-declaration.ts": "sha256:bafdfff8b00d7a244530a354525edb7f0e80bd2ff612b93ca3b9d6fff5a8913a",
146
+ "gen-ts/app-integrations-declaration.ts": "sha256:38ccc88408fa295274881eb9cee06feaa80c268cbaef52051263879fffa28c8c",
147
147
  "gen-ts/app-sign-in.ts": "sha256:bf88671c00e9d9badb5ffc961a7e803cf92cee5a98c2f6c9e3172bbf5aaf0424",
148
148
  "gen-ts/artifact-contract.ts": "sha256:4a6dc6d2caaeed4bb2066cc8a333e6e09e5a0d4745e0b1038293d54ab9403aeb",
149
149
  "gen-ts/binding-classification-digest-helper.ts": "sha256:40fc7d85b4c3f3ffc5caf7d7950c62d013c83ee42058c7453b2100d95817f167",
@@ -155,7 +155,7 @@
155
155
  "gen-ts/data-source.ts": "sha256:2566f0d5c72e44278869f55211cf3d433aeb3552e57ae0ace0ff0b85e769dde6",
156
156
  "gen-ts/deploy-request.ts": "sha256:9b0b58822f0e072c61df42c38679b34e449330d4909876c86b915afc6c1b05b6",
157
157
  "gen-ts/deploy-response.ts": "sha256:364d9f1acf32e491498237cdd3f402d5f8e102f208db7748034f4ec68c6a6137",
158
- "gen-ts/deployment-app-registration.ts": "sha256:bc6f5d8410b8dc9595f2feb2a32898ef0ef9f15d2a54da94cd362dc05e1bbd5f",
158
+ "gen-ts/deployment-app-registration.ts": "sha256:6ed162cf2ac7fe551b09334e4ab7498837356f10d3b29cafba284ea277d8f5c5",
159
159
  "gen-ts/deployment-continuation.ts": "sha256:cc9f63af4d0da5086a79103789e4f043783b75b4ed11c6512f044439a551a79c",
160
160
  "gen-ts/deployment-environment.ts": "sha256:fc08cabe2e52ebb93cfe84d2019994260f71734c33d0e50e7c63e40ef5b17cef",
161
161
  "gen-ts/deployment-preview-retirement.ts": "sha256:309421d08daf0ade163b6457ed0e5410ec6573c40a59423a6cce14780d9c38db",
@@ -173,10 +173,10 @@
173
173
  "gen-ts/index.ts": "sha256:5e903bb40fbf6599dc0d65171eaef7bc62b52d4cea090124a0c51ef876ad2909",
174
174
  "gen-ts/integration-connect-result.ts": "sha256:4b10f169b6e1af364e9b418de21caa9fadbfb77b43cbff77d741cfae0b52180c",
175
175
  "gen-ts/integration-error-details.ts": "sha256:ec082f2d302c1ef6c0bf768134c2bde4efa5fd5acf9cc0d3ccc4676e857ae43c",
176
- "gen-ts/integration-execute-input-rules.ts": "sha256:4ec1623b4cadf336070751dad850f276123d5464880b4ea0aff7d553ed644fb3",
177
- "gen-ts/integration-execute-request.ts": "sha256:3d0d9375176bdd9f3a5d5363ec0e6f24b63270fdbd34f95772e6402ef7915d53",
176
+ "gen-ts/integration-execute-input-rules.ts": "sha256:f82a02cd108037364a4c7ce91faf53dc5b8d9b6c5f3e2dbc7592f572ef9c7500",
177
+ "gen-ts/integration-execute-request.ts": "sha256:f3ccf9119dc1c554ade30173ec0376dc777097813e7ba691e2d687f595322c46",
178
178
  "gen-ts/integration-execute-result.ts": "sha256:e4772c27615de03284f7686aa49f92ff83b79a12587292129783443cf3ca0f80",
179
- "gen-ts/integration-operations.ts": "sha256:fc468da58ab6177e755bb21b0907d7c0a510d6dd291476f0abc1c832c7820734",
179
+ "gen-ts/integration-operations.ts": "sha256:2d67af36c2a926b63f0a2e66fb08e4b9d8ed3198f4d16ad42d0fd18e674bbdb3",
180
180
  "gen-ts/internal-configuration-request.ts": "sha256:7032437797473fa1c581d134adef2f3d4b4f4132f482cd17743e9d47097886bc",
181
181
  "gen-ts/kit-bundle-manifest.ts": "sha256:73f5271d2eb3b5ce91e28933c5ab6883615c72a3808aa386f860f58f6df8edf6",
182
182
  "gen-ts/kit-lock.ts": "sha256:06bddeaf1e2c449c9cc0dbbc696210355a9005349c604d4050eb22385fb4c22e",
@@ -8,11 +8,11 @@ export type DeclaredConnection = {
8
8
  'slack.message.delete'?: DeclaredModeChannelOperation;
9
9
  'slack.dm.send'?: AppModeChannelOperation;
10
10
  'slack.user.lookup'?: AppModeChannelOperation;
11
- 'gmail.thread.list'?: UserModeChannelOperation;
12
- 'gmail.thread.read'?: UserModeChannelOperation;
13
- 'gmail.message.read'?: UserModeChannelOperation;
11
+ 'gmail.thread.list'?: DeclaredModeChannelOperation;
12
+ 'gmail.thread.read'?: DeclaredModeChannelOperation;
13
+ 'gmail.message.read'?: DeclaredModeChannelOperation;
14
14
  'gmail.message.send'?: DeclaredModeChannelOperation;
15
- 'gmail.attachment.read'?: UserModeChannelOperation;
15
+ 'gmail.attachment.read'?: DeclaredModeChannelOperation;
16
16
  'basecamp.project.list'?: UserModeChannelOperation;
17
17
  'basecamp.todolist.list'?: UserModeChannelOperation;
18
18
  'basecamp.todo.list'?: UserModeChannelOperation;
@@ -146,7 +146,7 @@ export interface IntegrationPresentation {
146
146
  iconEmoji?: string;
147
147
  }
148
148
  /**
149
- * slack.message.post, slack.channel.history, slack.message.update, slack.message.delete: the app declares the mode — app (acts as the company's Slack app) or user (acts as the signed-in person). Since 0.102.0 (ADR 0045) gmail.message.send too, on the fixed `inbox` alias: user sends from the signed-in person's own mailbox after their consent; app sends from the company mailbox IT connected (the `installation` on the Gmail connection).
149
+ * slack.message.post, slack.channel.history, slack.message.update, slack.message.delete: the app declares the mode — app (acts as the company's Slack app) or user (acts as the signed-in person). Since 0.106.0 (ADR 0049) the four Gmail reads too, on the fixed `inbox` alias: user reads the signed-in person's own mailbox, app reads the company mailbox IT connected. Since 0.102.0 (ADR 0045) gmail.message.send too, on the fixed `inbox` alias: user sends from the signed-in person's own mailbox after their consent; app sends from the company mailbox IT connected (the `installation` on the Gmail connection).
150
150
  */
151
151
  export interface DeclaredModeChannelOperation {
152
152
  identity: IntegrationIdentityMode;
@@ -160,7 +160,7 @@ export interface AppModeChannelOperation {
160
160
  resources: ResourceNames;
161
161
  }
162
162
  /**
163
- * Every gmail.* operation except gmail.message.send, declared-mode since 0.102.0 (resource: the fixed `inbox` alias), since 0.76.0 every basecamp.* operation (resource: a project alias from the connection's `channels` map, or the fixed `account` alias for basecamp.project.list), since 0.78.0 every sharepoint.* operation (resource: a site alias from the connection's `channels` map) and since 0.86.0 every calendar.*, drive.*, docs.*, sheets.*, outlook.*, onedrive.*, excel.* and teams.* operation (resource: the provider's fixed alias for the person's own data, or for teams.* a channel alias from the connection's `channels` map): user mode only — the app acts as the signed-in person after their own consent.
163
+ * Since 0.106.0 no gmail.* operation (the send has been declared-mode since 0.102.0, the four reads since 0.106.0); since 0.76.0 every basecamp.* operation (resource: a project alias from the connection's `channels` map, or the fixed `account` alias for basecamp.project.list), since 0.78.0 every sharepoint.* operation (resource: a site alias from the connection's `channels` map) and since 0.86.0 every calendar.*, drive.*, docs.*, sheets.*, outlook.*, onedrive.*, excel.* and teams.* operation (resource: the provider's fixed alias for the person's own data, or for teams.* a channel alias from the connection's `channels` map): user mode only — the app acts as the signed-in person after their own consent.
164
164
  */
165
165
  export interface UserModeChannelOperation {
166
166
  identity: 'user';
@@ -1,7 +1,7 @@
1
- export declare const ContractRepositoryVersion: "0.104.0.0";
2
- export declare const ContractPublishVersion: "0.104.0";
3
- export declare const ContractPublishTag: "v0.104.0";
4
- export declare const ContractSchemasDigest: "sha256:e5912b264382f93a4a505d7d4d447c26909e5faae4f8ac9917ce6f351ee2c767";
1
+ export declare const ContractRepositoryVersion: "0.106.0.0";
2
+ export declare const ContractPublishVersion: "0.106.0";
3
+ export declare const ContractPublishTag: "v0.106.0";
4
+ export declare const ContractSchemasDigest: "sha256:af39e976761a7b4b7b7496b97562c1c0d75ddf4c1dae316abbeec1f96c261351";
5
5
  export declare const ContractAiFixturesDigest: "sha256:edac31c92e146ca1c40130f2fb5649db82e24b8cd1ac758153b90612052fddb6";
6
- export declare const ContractGeneratedGoDigest: "sha256:34ea126e68d00e09b3a61d3b65d699b451e8840d3ea1c40f4abd5b066fc5f1a4";
7
- export declare const ContractGeneratedTypeScriptDigest: "sha256:386ffce01eaf19ecd3ab800edf41c3288b672acf935b2a468be1edccc7980c7e";
6
+ export declare const ContractGeneratedGoDigest: "sha256:2e8b6f09caa663a5c29f7501b0e2e70fbc6ece8918074fbb76640e6727a48f15";
7
+ export declare const ContractGeneratedTypeScriptDigest: "sha256:e6f70790135277ed4586cbc14d411c08cb6fd6cc40b25343f4ee79eb4981ec6a";
@@ -1,9 +1,9 @@
1
1
  /* eslint-disable */
2
2
  // Code generated by tools/gen/gen.mjs from schema/*.json. DO NOT EDIT.
3
- export const ContractRepositoryVersion = "0.104.0.0";
4
- export const ContractPublishVersion = "0.104.0";
5
- export const ContractPublishTag = "v0.104.0";
6
- export const ContractSchemasDigest = "sha256:e5912b264382f93a4a505d7d4d447c26909e5faae4f8ac9917ce6f351ee2c767";
3
+ export const ContractRepositoryVersion = "0.106.0.0";
4
+ export const ContractPublishVersion = "0.106.0";
5
+ export const ContractPublishTag = "v0.106.0";
6
+ export const ContractSchemasDigest = "sha256:af39e976761a7b4b7b7496b97562c1c0d75ddf4c1dae316abbeec1f96c261351";
7
7
  export const ContractAiFixturesDigest = "sha256:edac31c92e146ca1c40130f2fb5649db82e24b8cd1ac758153b90612052fddb6";
8
- export const ContractGeneratedGoDigest = "sha256:34ea126e68d00e09b3a61d3b65d699b451e8840d3ea1c40f4abd5b066fc5f1a4";
9
- export const ContractGeneratedTypeScriptDigest = "sha256:386ffce01eaf19ecd3ab800edf41c3288b672acf935b2a468be1edccc7980c7e";
8
+ export const ContractGeneratedGoDigest = "sha256:2e8b6f09caa663a5c29f7501b0e2e70fbc6ece8918074fbb76640e6727a48f15";
9
+ export const ContractGeneratedTypeScriptDigest = "sha256:e6f70790135277ed4586cbc14d411c08cb6fd6cc40b25343f4ee79eb4981ec6a";
@@ -102,6 +102,10 @@ export interface EmailArrivalTrigger {
102
102
  type: 'email.arrived';
103
103
  connection: string;
104
104
  resource: 'inbox';
105
+ /**
106
+ * 0.106.0 (ADR 0049), Gmail only: whose mailbox the trigger watches. `app` is the company mailbox IT connected on the connection, with no owner and no personal consent; `user` and absent are the app owner's own mailbox, as before.
107
+ */
108
+ mode?: 'user' | 'app';
105
109
  filter?: {
106
110
  from?: string;
107
111
  subjectContains?: string;
@@ -342,21 +342,27 @@ export declare const IntegrationExecuteInputRules: {
342
342
  readonly minItems: 1;
343
343
  readonly maxItems: 5;
344
344
  readonly fields: {
345
+ readonly file: {
346
+ readonly kind: "string";
347
+ readonly required: false;
348
+ readonly minLength: 1;
349
+ readonly maxLength: 1024;
350
+ };
345
351
  readonly filename: {
346
352
  readonly kind: "string";
347
- readonly required: true;
353
+ readonly required: false;
348
354
  readonly minLength: 1;
349
355
  readonly maxLength: 255;
350
356
  };
351
357
  readonly contentType: {
352
358
  readonly kind: "string";
353
- readonly required: true;
359
+ readonly required: false;
354
360
  readonly minLength: 1;
355
361
  readonly maxLength: 128;
356
362
  };
357
363
  readonly dataBase64: {
358
364
  readonly kind: "string";
359
- readonly required: true;
365
+ readonly required: false;
360
366
  readonly minLength: 1;
361
367
  readonly maxLength: 2796203;
362
368
  };
@@ -1484,21 +1490,27 @@ export declare const IntegrationExecuteInputRules: {
1484
1490
  readonly minItems: 1;
1485
1491
  readonly maxItems: 5;
1486
1492
  readonly fields: {
1493
+ readonly file: {
1494
+ readonly kind: "string";
1495
+ readonly required: false;
1496
+ readonly minLength: 1;
1497
+ readonly maxLength: 1024;
1498
+ };
1487
1499
  readonly filename: {
1488
1500
  readonly kind: "string";
1489
- readonly required: true;
1501
+ readonly required: false;
1490
1502
  readonly minLength: 1;
1491
1503
  readonly maxLength: 255;
1492
1504
  };
1493
1505
  readonly contentType: {
1494
1506
  readonly kind: "string";
1495
- readonly required: true;
1507
+ readonly required: false;
1496
1508
  readonly minLength: 1;
1497
1509
  readonly maxLength: 128;
1498
1510
  };
1499
1511
  readonly dataBase64: {
1500
1512
  readonly kind: "string";
1501
- readonly required: true;
1513
+ readonly required: false;
1502
1514
  readonly minLength: 1;
1503
1515
  readonly maxLength: 2796203;
1504
1516
  };
@@ -1527,21 +1539,27 @@ export declare const IntegrationExecuteInputRules: {
1527
1539
  readonly minItems: 1;
1528
1540
  readonly maxItems: 5;
1529
1541
  readonly fields: {
1542
+ readonly file: {
1543
+ readonly kind: "string";
1544
+ readonly required: false;
1545
+ readonly minLength: 1;
1546
+ readonly maxLength: 1024;
1547
+ };
1530
1548
  readonly filename: {
1531
1549
  readonly kind: "string";
1532
- readonly required: true;
1550
+ readonly required: false;
1533
1551
  readonly minLength: 1;
1534
1552
  readonly maxLength: 255;
1535
1553
  };
1536
1554
  readonly contentType: {
1537
1555
  readonly kind: "string";
1538
- readonly required: true;
1556
+ readonly required: false;
1539
1557
  readonly minLength: 1;
1540
1558
  readonly maxLength: 128;
1541
1559
  };
1542
1560
  readonly dataBase64: {
1543
1561
  readonly kind: "string";
1544
- readonly required: true;
1562
+ readonly required: false;
1545
1563
  readonly minLength: 1;
1546
1564
  readonly maxLength: 2796203;
1547
1565
  };
@@ -77,9 +77,10 @@ export const IntegrationExecuteInputRules = {
77
77
  text: { kind: "string", required: true, minLength: 1, maxLength: 4000 },
78
78
  html: { kind: "string", required: false, minLength: 1, maxLength: 65536 },
79
79
  attachments: { kind: "object[]", required: false, minItems: 1, maxItems: 5, fields: {
80
- filename: { kind: "string", required: true, minLength: 1, maxLength: 255 },
81
- contentType: { kind: "string", required: true, minLength: 1, maxLength: 128 },
82
- dataBase64: { kind: "string", required: true, minLength: 1, maxLength: 2796203 },
80
+ file: { kind: "string", required: false, minLength: 1, maxLength: 1024 },
81
+ filename: { kind: "string", required: false, minLength: 1, maxLength: 255 },
82
+ contentType: { kind: "string", required: false, minLength: 1, maxLength: 128 },
83
+ dataBase64: { kind: "string", required: false, minLength: 1, maxLength: 2796203 },
83
84
  } },
84
85
  replyToMessageId: { kind: "string", required: false, minLength: 0, pattern: "^[A-Za-z0-9_-]{1,64}$" },
85
86
  replyTo: { kind: "string", required: false, minLength: 3, maxLength: 254, pattern: "^[^\\s@<>,;\"]+@[^\\s@<>,;\"]+\\.[^\\s@<>,;\"]+$" },
@@ -461,9 +462,10 @@ export const IntegrationExecuteInputRules = {
461
462
  text: { kind: "string", required: true, minLength: 1, maxLength: 4000 },
462
463
  html: { kind: "string", required: false, minLength: 1, maxLength: 65536 },
463
464
  attachments: { kind: "object[]", required: false, minItems: 1, maxItems: 5, fields: {
464
- filename: { kind: "string", required: true, minLength: 1, maxLength: 255 },
465
- contentType: { kind: "string", required: true, minLength: 1, maxLength: 128 },
466
- dataBase64: { kind: "string", required: true, minLength: 1, maxLength: 2796203 },
465
+ file: { kind: "string", required: false, minLength: 1, maxLength: 1024 },
466
+ filename: { kind: "string", required: false, minLength: 1, maxLength: 255 },
467
+ contentType: { kind: "string", required: false, minLength: 1, maxLength: 128 },
468
+ dataBase64: { kind: "string", required: false, minLength: 1, maxLength: 2796203 },
467
469
  } },
468
470
  },
469
471
  },
@@ -473,9 +475,10 @@ export const IntegrationExecuteInputRules = {
473
475
  messageId: { kind: "string", required: true, minLength: 0, pattern: "^[A-Za-z0-9._=-]{1,256}$" },
474
476
  text: { kind: "string", required: true, minLength: 1, maxLength: 4000 },
475
477
  attachments: { kind: "object[]", required: false, minItems: 1, maxItems: 5, fields: {
476
- filename: { kind: "string", required: true, minLength: 1, maxLength: 255 },
477
- contentType: { kind: "string", required: true, minLength: 1, maxLength: 128 },
478
- dataBase64: { kind: "string", required: true, minLength: 1, maxLength: 2796203 },
478
+ file: { kind: "string", required: false, minLength: 1, maxLength: 1024 },
479
+ filename: { kind: "string", required: false, minLength: 1, maxLength: 255 },
480
+ contentType: { kind: "string", required: false, minLength: 1, maxLength: 128 },
481
+ dataBase64: { kind: "string", required: false, minLength: 1, maxLength: 2796203 },
479
482
  } },
480
483
  },
481
484
  },
@@ -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; since 0.104.0 `to` may be absent when cc or bcc names a recipient — governance refuses a send with no recipient at all), cc?, bcc?, subject 1..256 chars, text 1..4000 chars, html? <= 64 KiB, attachments? 1..5 of <= 2 MiB each as base64, replyToMessageId?, replyTo? (one address), fromName? 1..120 chars, inReplyTo? (an RFC Message-ID; not with 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). Google Workspace and Microsoft 365 (0.86.0, ADR 0032; every operation user mode; the seven personal providers run on their fixed alias, teams.* on a channel alias): calendar.calendar.list {pageToken?}; calendar.event.list {calendarId?, timeMin, timeMax, q?, pageToken?} (RFC 3339 date-times with a zone); calendar.event.read {calendarId?, eventId}; calendar.event.create {calendarId?, title 1..256, start, end, timeZone?, allDay?, description? <= 8000, location? <= 512, attendees? 1..100 emails, notify?} with a required idempotencyKey; calendar.event.update {calendarId?, eventId, title?, start?, end?, timeZone?, description?, location?, attendees?, notify?} and calendar.event.cancel {calendarId?, eventId, notify?} (both idempotent at Google, so no key); drive.file.list {folderId?, pageToken?}; drive.file.search {q, pageToken?}; drive.file.read {fileId, exportAs? text|csv|pdf} (exportAs renders a Google Docs, Sheets or Slides file; a binary file is downloaded as is); drive.file.upload {filename, contentType, dataBase64 <= 2 MiB, folderId?, conflict?} and drive.folder.create {title, folderId?} with a required idempotencyKey; docs.document.read {documentId}; docs.document.create {title, text? <= 64 KiB} with a required idempotencyKey; docs.document.append {documentId, text <= 64 KiB} and docs.text.replace {documentId, replacements 1..64 of {find 1..512, replace <= 4000, matchCase?}} (no key: the app owns the document's state); sheets.spreadsheet.read {spreadsheetId}; sheets.values.read {spreadsheetId, range} (A1 notation, 1..256); sheets.values.append {spreadsheetId, range, values, valueInput? raw|user} with a required idempotencyKey; sheets.values.update {spreadsheetId, range, values, valueInput?} and sheets.values.clear {spreadsheetId, range} (no key); sheets.spreadsheet.create {title, sheets? 1..20 titles} with a required idempotencyKey (`values` is 1..1000 rows of 1..100 cells, each a string <= 4000, a number, a boolean or null); outlook.message.list {folder?, q?, pageToken?}; outlook.message.read {messageId}; outlook.attachment.read {messageId, attachmentId}; outlook.message.send {to, cc?, bcc?, subject, text 1..4000, html?, attachments?} and outlook.message.reply {messageId, text 1..4000, attachments?} (0.95.0: existing EmailAttachment bounds; recipients remain provider-resolved) with a required idempotencyKey like gmail.message.send; outlook.event.list {start, end, pageToken?}; outlook.event.read {eventId}; outlook.event.create (as calendar.event.create without calendarId) with a required idempotencyKey; outlook.event.update (as calendar.event.update without calendarId) and outlook.event.cancel {eventId, comment? <= 4000} (no key); onedrive.file.list, onedrive.file.search, onedrive.file.read {fileId}, onedrive.file.upload and onedrive.folder.create as the drive.* operations (Graph ids, no exportAs); excel.worksheet.list {fileId, driveId?}; excel.range.read {fileId, driveId?, worksheet, address? 1..64} (no address: the used range); excel.range.update {fileId, driveId?, worksheet, address, values} (no key: a PATCH); excel.table.list {fileId, driveId?, worksheet?}; excel.row.add {fileId, driveId?, tableName, values} and excel.worksheet.add {fileId, driveId?, title} with a required idempotencyKey; teams.message.list {pageToken?}; teams.message.post {text 1..4000, html?} and teams.reply.post {messageId, text, html?} with a required idempotencyKey. An Outlook or Teams `messageId` is a Graph id (the shared field widens to ^[A-Za-z0-9._=-]{1,256}$; the Gmail and Slack operations narrow it back to ^[A-Za-z0-9._-]{1,64}$ in their then-blocks, so their rules are unchanged), and `text` widens to 64 KiB for the Docs body (every other operation that takes it narrows it back to 4000). 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, drive.file.upload or onedrive.file.upload (its 2 MiB file as base64; governance sizes it like the Gmail attachment route), an outlook.message.send or outlook.message.reply with attachments (the Gmail route), a docs.document.create or docs.document.append (its 64 KiB body; governance allows 80 KiB there, as for basecamp.message.post) and a sheets.values.append, sheets.values.update, excel.range.update or excel.row.add (its 64 KiB `values`), 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; since 0.104.0 `to` may be absent when cc or bcc names a recipient — governance refuses a send with no recipient at all), cc?, bcc?, subject 1..256 chars, text 1..4000 chars, html? <= 64 KiB, attachments? 1..5, each {filename, contentType, dataBase64} (inline, 2 MiB in total) or since 0.105.0 {file, filename?, contentType?} (a private app file by path, deployed apps only; 18 MiB in total), replyToMessageId?, replyTo? (one address), fromName? 1..120 chars, inReplyTo? (an RFC Message-ID; not with 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). Google Workspace and Microsoft 365 (0.86.0, ADR 0032; every operation user mode; the seven personal providers run on their fixed alias, teams.* on a channel alias): calendar.calendar.list {pageToken?}; calendar.event.list {calendarId?, timeMin, timeMax, q?, pageToken?} (RFC 3339 date-times with a zone); calendar.event.read {calendarId?, eventId}; calendar.event.create {calendarId?, title 1..256, start, end, timeZone?, allDay?, description? <= 8000, location? <= 512, attendees? 1..100 emails, notify?} with a required idempotencyKey; calendar.event.update {calendarId?, eventId, title?, start?, end?, timeZone?, description?, location?, attendees?, notify?} and calendar.event.cancel {calendarId?, eventId, notify?} (both idempotent at Google, so no key); drive.file.list {folderId?, pageToken?}; drive.file.search {q, pageToken?}; drive.file.read {fileId, exportAs? text|csv|pdf} (exportAs renders a Google Docs, Sheets or Slides file; a binary file is downloaded as is); drive.file.upload {filename, contentType, dataBase64 <= 2 MiB, folderId?, conflict?} and drive.folder.create {title, folderId?} with a required idempotencyKey; docs.document.read {documentId}; docs.document.create {title, text? <= 64 KiB} with a required idempotencyKey; docs.document.append {documentId, text <= 64 KiB} and docs.text.replace {documentId, replacements 1..64 of {find 1..512, replace <= 4000, matchCase?}} (no key: the app owns the document's state); sheets.spreadsheet.read {spreadsheetId}; sheets.values.read {spreadsheetId, range} (A1 notation, 1..256); sheets.values.append {spreadsheetId, range, values, valueInput? raw|user} with a required idempotencyKey; sheets.values.update {spreadsheetId, range, values, valueInput?} and sheets.values.clear {spreadsheetId, range} (no key); sheets.spreadsheet.create {title, sheets? 1..20 titles} with a required idempotencyKey (`values` is 1..1000 rows of 1..100 cells, each a string <= 4000, a number, a boolean or null); outlook.message.list {folder?, q?, pageToken?}; outlook.message.read {messageId}; outlook.attachment.read {messageId, attachmentId}; outlook.message.send {to, cc?, bcc?, subject, text 1..4000, html?, attachments?} and outlook.message.reply {messageId, text 1..4000, attachments?} (0.95.0: existing EmailAttachment bounds; recipients remain provider-resolved) with a required idempotencyKey like gmail.message.send; outlook.event.list {start, end, pageToken?}; outlook.event.read {eventId}; outlook.event.create (as calendar.event.create without calendarId) with a required idempotencyKey; outlook.event.update (as calendar.event.update without calendarId) and outlook.event.cancel {eventId, comment? <= 4000} (no key); onedrive.file.list, onedrive.file.search, onedrive.file.read {fileId}, onedrive.file.upload and onedrive.folder.create as the drive.* operations (Graph ids, no exportAs); excel.worksheet.list {fileId, driveId?}; excel.range.read {fileId, driveId?, worksheet, address? 1..64} (no address: the used range); excel.range.update {fileId, driveId?, worksheet, address, values} (no key: a PATCH); excel.table.list {fileId, driveId?, worksheet?}; excel.row.add {fileId, driveId?, tableName, values} and excel.worksheet.add {fileId, driveId?, title} with a required idempotencyKey; teams.message.list {pageToken?}; teams.message.post {text 1..4000, html?} and teams.reply.post {messageId, text, html?} with a required idempotencyKey. An Outlook or Teams `messageId` is a Graph id (the shared field widens to ^[A-Za-z0-9._=-]{1,256}$; the Gmail and Slack operations narrow it back to ^[A-Za-z0-9._-]{1,64}$ in their then-blocks, so their rules are unchanged), and `text` widens to 64 KiB for the Docs body (every other operation that takes it narrows it back to 4000). 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, drive.file.upload or onedrive.file.upload (its 2 MiB file as base64; governance sizes it like the Gmail attachment route), an outlook.message.send or outlook.message.reply with attachments (the Gmail route), a docs.document.create or docs.document.append (its 64 KiB body; governance allows 80 KiB there, as for basecamp.message.post) and a sheets.values.append, sheets.values.update, excel.range.update or excel.row.add (its 64 KiB `values`), total deadline 10 s.
3
3
  */
4
4
  export type IntegrationExecuteRequest = {
5
5
  /**
@@ -7,7 +7,7 @@ export type IntegrationExecuteRequest = {
7
7
  */
8
8
  connection: string;
9
9
  /**
10
- * Closed operation set. Extending it is a contracts change. 0.102.0 (ADR 0045) makes gmail.message.send declared-mode: user sends from the signed-in person's mailbox, app sends from the company mailbox IT connected (the installation on the Gmail connection); every other gmail.* operation stays user mode. 0.86.0 (ADR 0032) adds the forty-five Google Workspace and Microsoft 365 operations, every one user mode only: on the provider's fixed alias for the signed-in person's own data, calendar.calendar.list, calendar.event.list, calendar.event.read, calendar.event.create (a receipted write: explicit action, idempotencyKey), calendar.event.update and calendar.event.cancel (idempotent at Google, no key); drive.file.list, drive.file.search, drive.file.read, drive.file.upload and drive.folder.create (receipted writes); docs.document.read, docs.document.create (receipted), docs.document.append and docs.text.replace (no key); sheets.spreadsheet.read, sheets.values.read, sheets.values.append (receipted), sheets.values.update and sheets.values.clear (no key), sheets.spreadsheet.create (receipted); outlook.message.list, outlook.message.read, outlook.attachment.read, outlook.message.send and outlook.message.reply (receipted sends, as gmail.message.send), outlook.event.list, outlook.event.read, outlook.event.create (receipted), outlook.event.update and outlook.event.cancel (no key); onedrive.file.list, onedrive.file.search, onedrive.file.read, onedrive.file.upload and onedrive.folder.create (receipted); excel.worksheet.list, excel.range.read, excel.range.update (no key), excel.table.list, excel.row.add and excel.worksheet.add (receipted); and on a Teams channel alias (a key of the connection's `channels` map) teams.message.list, teams.message.post and teams.reply.post (receipted). 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. 0.82.0 (ADR 0028) adds phone.sms.list (app mode only, a number alias from a phone connection's `channels` map: the SMS received on that number since a time) and warehouse.table.append (app mode only on a database connection, a receipted write with idempotencyKey: rows appended to a view alias whose mapping says `access: append`). 0.83.0 (ADR 0029) adds phone.number.get (app mode only, read-only, no input, on a number alias from a phone connection's `channels` map: the E.164 number that alias maps to, so the app — or a computer-use agent it drives — can type it into a portal that signs in by mobile number). 0.85.0 (ADR 0031) raises warehouse.table.append's `rows` from 1..500 to 1..5000 records (the byte cap stays governance's, now 8 MiB), so a downloaded report of a few thousand rows lands in one call.
10
+ * Closed operation set. Extending it is a contracts change. 0.106.0 (ADR 0049) makes the four Gmail reads (gmail.thread.list, gmail.thread.read, gmail.message.read, gmail.attachment.read) declared-mode like the send: user reads the signed-in person's mailbox, app reads the company mailbox IT connected, when its sign-in granted reading. 0.102.0 (ADR 0045) makes gmail.message.send declared-mode: user sends from the signed-in person's mailbox, app sends from the company mailbox IT connected (the installation on the Gmail connection); every other gmail.* operation stays user mode. 0.86.0 (ADR 0032) adds the forty-five Google Workspace and Microsoft 365 operations, every one user mode only: on the provider's fixed alias for the signed-in person's own data, calendar.calendar.list, calendar.event.list, calendar.event.read, calendar.event.create (a receipted write: explicit action, idempotencyKey), calendar.event.update and calendar.event.cancel (idempotent at Google, no key); drive.file.list, drive.file.search, drive.file.read, drive.file.upload and drive.folder.create (receipted writes); docs.document.read, docs.document.create (receipted), docs.document.append and docs.text.replace (no key); sheets.spreadsheet.read, sheets.values.read, sheets.values.append (receipted), sheets.values.update and sheets.values.clear (no key), sheets.spreadsheet.create (receipted); outlook.message.list, outlook.message.read, outlook.attachment.read, outlook.message.send and outlook.message.reply (receipted sends, as gmail.message.send), outlook.event.list, outlook.event.read, outlook.event.create (receipted), outlook.event.update and outlook.event.cancel (no key); onedrive.file.list, onedrive.file.search, onedrive.file.read, onedrive.file.upload and onedrive.folder.create (receipted); excel.worksheet.list, excel.range.read, excel.range.update (no key), excel.table.list, excel.row.add and excel.worksheet.add (receipted); and on a Teams channel alias (a key of the connection's `channels` map) teams.message.list, teams.message.post and teams.reply.post (receipted). 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. 0.82.0 (ADR 0028) adds phone.sms.list (app mode only, a number alias from a phone connection's `channels` map: the SMS received on that number since a time) and warehouse.table.append (app mode only on a database connection, a receipted write with idempotencyKey: rows appended to a view alias whose mapping says `access: append`). 0.83.0 (ADR 0029) adds phone.number.get (app mode only, read-only, no input, on a number alias from a phone connection's `channels` map: the E.164 number that alias maps to, so the app — or a computer-use agent it drives — can type it into a portal that signs in by mobile number). 0.85.0 (ADR 0031) raises warehouse.table.append's `rows` from 1..500 to 1..5000 records (the byte cap stays governance's, now 8 MiB), so a downloaded report of a few thousand rows lands in one call.
11
11
  */
12
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' | 'phone.sms.list' | 'phone.number.get' | 'calendar.calendar.list' | 'calendar.event.list' | 'calendar.event.read' | 'calendar.event.create' | 'calendar.event.update' | 'calendar.event.cancel' | 'drive.file.list' | 'drive.file.search' | 'drive.file.read' | 'drive.file.upload' | 'drive.folder.create' | 'docs.document.read' | 'docs.document.create' | 'docs.document.append' | 'docs.text.replace' | 'sheets.spreadsheet.read' | 'sheets.values.read' | 'sheets.values.append' | 'sheets.values.update' | 'sheets.values.clear' | 'sheets.spreadsheet.create' | 'outlook.message.list' | 'outlook.message.read' | 'outlook.attachment.read' | 'outlook.message.send' | 'outlook.message.reply' | 'outlook.event.list' | 'outlook.event.read' | 'outlook.event.create' | 'outlook.event.update' | 'outlook.event.cancel' | 'onedrive.file.list' | 'onedrive.file.search' | 'onedrive.file.read' | 'onedrive.file.upload' | 'onedrive.folder.create' | 'excel.worksheet.list' | 'excel.range.read' | 'excel.range.update' | 'excel.table.list' | 'excel.row.add' | 'excel.worksheet.add' | 'teams.message.list' | 'teams.message.post' | 'teams.reply.post' | 'warehouse.view.read' | 'warehouse.table.append' | 'service.request' | 'zohobooks.contact.list' | 'zohobooks.purchaseorder.list' | 'zohobooks.purchaseorder.read' | 'zohobooks.bill.create' | 'zohobooks.bill.read' | 'zohobooks.invoice.create' | 'zohobooks.invoice.read' | 'zohobooks.invoice.pdf' | 'zohobooks.salesorder.list' | 'zohobooks.salesorder.read' | 'zohobooks.invoice.reviseDraft' | 'zohobooks.invoice.markSent' | 'zohobooks.creditnote.create' | 'zohobooks.creditnote.read' | 'mcp.tool.call';
13
13
  /**
@@ -98,7 +98,7 @@ export type IntegrationExecuteRequest = {
98
98
  */
99
99
  html?: string;
100
100
  /**
101
- * gmail.message.send (0.71.0): files to attach, 1..5, each at most 2 MiB (2,796,203 base64 characters). outlook.message.send (0.86.0): the same, 1..5.
101
+ * gmail.message.send (0.71.0): files to attach, 1..5; inline ones (`dataBase64`) total at most 2 MiB, and since 0.105.0 one may instead name a private app file (`file`), 18 MiB in total. outlook.message.send (0.86.0): inline files only, 1..5.
102
102
  *
103
103
  * @minItems 1
104
104
  * @maxItems 5
@@ -411,7 +411,7 @@ export type IntegrationExecuteRequest = {
411
411
  */
412
412
  idempotencyKey?: string;
413
413
  /**
414
- * The declared-mode operations only (slack.message.post since 0.55.0; slack.channel.history, slack.message.update and slack.message.delete since 0.71.0; gmail.message.send since 0.102.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; for gmail.message.send, the company mailbox IT connected) or user (the signed-in person, after their consent; no name, no footer; for gmail.message.send, their own mailbox). 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. MCP accepts explicit user only; shared app mode is deferred.
414
+ * The declared-mode operations only (slack.message.post since 0.55.0; slack.channel.history, slack.message.update and slack.message.delete since 0.71.0; gmail.message.send since 0.102.0; gmail.thread.list, gmail.thread.read, gmail.message.read and gmail.attachment.read since 0.106.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; for gmail.message.send, the company mailbox IT connected) or user (the signed-in person, after their consent; no name, no footer; for gmail.message.send, their own mailbox). A Gmail read in app mode reads the company mailbox; in user mode, and with no mode when the app holds only that grant, the signed-in person's own. 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. MCP accepts explicit user only; shared app mode is deferred.
415
415
  */
416
416
  mode?: 'app' | 'user';
417
417
  /**
@@ -461,21 +461,25 @@ export type WarehouseAggregateFn = 'count' | 'sum' | 'avg' | 'min' | 'max';
461
461
  */
462
462
  export type WarehouseOrderDirection = 'asc' | 'desc';
463
463
  /**
464
- * One file attached to a gmail.message.send (0.71.0) or an outlook.message.send (0.86.0), carried inline as base64. At most 2 MiB of bytes, which is 2,796,203 base64 characters.
464
+ * One file attached to a gmail.message.send (0.71.0) or an outlook.message.send (0.86.0). Either carried inline as base64 — `filename`, `contentType` and `dataBase64`, at most 2 MiB of bytes, which is 2,796,203 base64 characters — or, on a gmail.message.send from a deployed app (0.105.0), named by `file`: the path of a private file of the app (isomorph.files), which the app's gateway checks the caller may read and hands to governance as a short-lived link, so no file bytes ride the request; `filename` and `contentType` then default to the file's own. Exactly one of `dataBase64` and `file`: a cross-field rule governance enforces (no field is required here). The Outlook sends take the inline form only.
465
465
  */
466
466
  export interface EmailAttachment {
467
+ /**
468
+ * gmail.message.send (0.105.0): the path of one of the app's private files, as isomorph.files names it (e.g. letters/offer.pdf). The inline and referenced attachments of one email total at most 18 MiB decoded.
469
+ */
470
+ file?: string;
467
471
  /**
468
472
  * The name the recipient sees.
469
473
  */
470
- filename: string;
474
+ filename?: string;
471
475
  /**
472
476
  * The MIME type, e.g. application/pdf.
473
477
  */
474
- contentType: string;
478
+ contentType?: string;
475
479
  /**
476
480
  * The file bytes, standard base64.
477
481
  */
478
- dataBase64: string;
482
+ dataBase64?: string;
479
483
  }
480
484
  /**
481
485
  * 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.
@@ -44,23 +44,23 @@ export declare const IntegrationOperations: {
44
44
  };
45
45
  readonly "gmail.thread.list": {
46
46
  readonly kind: "saas";
47
- readonly identities: readonly ["user"];
47
+ readonly identities: readonly ["user", "app"];
48
48
  readonly readOnly: true;
49
- readonly declaredMode: false;
49
+ readonly declaredMode: true;
50
50
  readonly idempotencyKey: false;
51
51
  };
52
52
  readonly "gmail.thread.read": {
53
53
  readonly kind: "saas";
54
- readonly identities: readonly ["user"];
54
+ readonly identities: readonly ["user", "app"];
55
55
  readonly readOnly: true;
56
- readonly declaredMode: false;
56
+ readonly declaredMode: true;
57
57
  readonly idempotencyKey: false;
58
58
  };
59
59
  readonly "gmail.message.read": {
60
60
  readonly kind: "saas";
61
- readonly identities: readonly ["user"];
61
+ readonly identities: readonly ["user", "app"];
62
62
  readonly readOnly: true;
63
- readonly declaredMode: false;
63
+ readonly declaredMode: true;
64
64
  readonly idempotencyKey: false;
65
65
  };
66
66
  readonly "gmail.message.send": {
@@ -72,9 +72,9 @@ export declare const IntegrationOperations: {
72
72
  };
73
73
  readonly "gmail.attachment.read": {
74
74
  readonly kind: "saas";
75
- readonly identities: readonly ["user"];
75
+ readonly identities: readonly ["user", "app"];
76
76
  readonly readOnly: true;
77
- readonly declaredMode: false;
77
+ readonly declaredMode: true;
78
78
  readonly idempotencyKey: false;
79
79
  };
80
80
  readonly "basecamp.project.list": {
@@ -63,28 +63,31 @@ export const IntegrationOperations = {
63
63
  "gmail.thread.list": {
64
64
  "kind": "saas",
65
65
  "identities": [
66
- "user"
66
+ "user",
67
+ "app"
67
68
  ],
68
69
  "readOnly": true,
69
- "declaredMode": false,
70
+ "declaredMode": true,
70
71
  "idempotencyKey": false
71
72
  },
72
73
  "gmail.thread.read": {
73
74
  "kind": "saas",
74
75
  "identities": [
75
- "user"
76
+ "user",
77
+ "app"
76
78
  ],
77
79
  "readOnly": true,
78
- "declaredMode": false,
80
+ "declaredMode": true,
79
81
  "idempotencyKey": false
80
82
  },
81
83
  "gmail.message.read": {
82
84
  "kind": "saas",
83
85
  "identities": [
84
- "user"
86
+ "user",
87
+ "app"
85
88
  ],
86
89
  "readOnly": true,
87
- "declaredMode": false,
90
+ "declaredMode": true,
88
91
  "idempotencyKey": false
89
92
  },
90
93
  "gmail.message.send": {
@@ -100,10 +103,11 @@ export const IntegrationOperations = {
100
103
  "gmail.attachment.read": {
101
104
  "kind": "saas",
102
105
  "identities": [
103
- "user"
106
+ "user",
107
+ "app"
104
108
  ],
105
109
  "readOnly": true,
106
- "declaredMode": false,
110
+ "declaredMode": true,
107
111
  "idempotencyKey": false
108
112
  },
109
113
  "basecamp.project.list": {
@@ -11,11 +11,11 @@ export type DeclaredConnection = {
11
11
  'slack.message.delete'?: DeclaredModeChannelOperation;
12
12
  'slack.dm.send'?: AppModeChannelOperation;
13
13
  'slack.user.lookup'?: AppModeChannelOperation;
14
- 'gmail.thread.list'?: UserModeChannelOperation;
15
- 'gmail.thread.read'?: UserModeChannelOperation;
16
- 'gmail.message.read'?: UserModeChannelOperation;
14
+ 'gmail.thread.list'?: DeclaredModeChannelOperation;
15
+ 'gmail.thread.read'?: DeclaredModeChannelOperation;
16
+ 'gmail.message.read'?: DeclaredModeChannelOperation;
17
17
  'gmail.message.send'?: DeclaredModeChannelOperation;
18
- 'gmail.attachment.read'?: UserModeChannelOperation;
18
+ 'gmail.attachment.read'?: DeclaredModeChannelOperation;
19
19
  'basecamp.project.list'?: UserModeChannelOperation;
20
20
  'basecamp.todolist.list'?: UserModeChannelOperation;
21
21
  'basecamp.todo.list'?: UserModeChannelOperation;
@@ -150,7 +150,7 @@ export interface IntegrationPresentation {
150
150
  iconEmoji?: string;
151
151
  }
152
152
  /**
153
- * slack.message.post, slack.channel.history, slack.message.update, slack.message.delete: the app declares the mode — app (acts as the company's Slack app) or user (acts as the signed-in person). Since 0.102.0 (ADR 0045) gmail.message.send too, on the fixed `inbox` alias: user sends from the signed-in person's own mailbox after their consent; app sends from the company mailbox IT connected (the `installation` on the Gmail connection).
153
+ * slack.message.post, slack.channel.history, slack.message.update, slack.message.delete: the app declares the mode — app (acts as the company's Slack app) or user (acts as the signed-in person). Since 0.106.0 (ADR 0049) the four Gmail reads too, on the fixed `inbox` alias: user reads the signed-in person's own mailbox, app reads the company mailbox IT connected. Since 0.102.0 (ADR 0045) gmail.message.send too, on the fixed `inbox` alias: user sends from the signed-in person's own mailbox after their consent; app sends from the company mailbox IT connected (the `installation` on the Gmail connection).
154
154
  */
155
155
  export interface DeclaredModeChannelOperation {
156
156
  identity: IntegrationIdentityMode;
@@ -164,7 +164,7 @@ export interface AppModeChannelOperation {
164
164
  resources: ResourceNames;
165
165
  }
166
166
  /**
167
- * Every gmail.* operation except gmail.message.send, declared-mode since 0.102.0 (resource: the fixed `inbox` alias), since 0.76.0 every basecamp.* operation (resource: a project alias from the connection's `channels` map, or the fixed `account` alias for basecamp.project.list), since 0.78.0 every sharepoint.* operation (resource: a site alias from the connection's `channels` map) and since 0.86.0 every calendar.*, drive.*, docs.*, sheets.*, outlook.*, onedrive.*, excel.* and teams.* operation (resource: the provider's fixed alias for the person's own data, or for teams.* a channel alias from the connection's `channels` map): user mode only — the app acts as the signed-in person after their own consent.
167
+ * Since 0.106.0 no gmail.* operation (the send has been declared-mode since 0.102.0, the four reads since 0.106.0); since 0.76.0 every basecamp.* operation (resource: a project alias from the connection's `channels` map, or the fixed `account` alias for basecamp.project.list), since 0.78.0 every sharepoint.* operation (resource: a site alias from the connection's `channels` map) and since 0.86.0 every calendar.*, drive.*, docs.*, sheets.*, outlook.*, onedrive.*, excel.* and teams.* operation (resource: the provider's fixed alias for the person's own data, or for teams.* a channel alias from the connection's `channels` map): user mode only — the app acts as the signed-in person after their own consent.
168
168
  */
169
169
  export interface UserModeChannelOperation {
170
170
  identity: 'user';
@@ -1,10 +1,10 @@
1
1
  /* eslint-disable */
2
2
  // Code generated by tools/gen/gen.mjs from schema/*.json. DO NOT EDIT.
3
3
 
4
- export const ContractRepositoryVersion = "0.104.0.0" as const;
5
- export const ContractPublishVersion = "0.104.0" as const;
6
- export const ContractPublishTag = "v0.104.0" as const;
7
- export const ContractSchemasDigest = "sha256:e5912b264382f93a4a505d7d4d447c26909e5faae4f8ac9917ce6f351ee2c767" as const;
4
+ export const ContractRepositoryVersion = "0.106.0.0" as const;
5
+ export const ContractPublishVersion = "0.106.0" as const;
6
+ export const ContractPublishTag = "v0.106.0" as const;
7
+ export const ContractSchemasDigest = "sha256:af39e976761a7b4b7b7496b97562c1c0d75ddf4c1dae316abbeec1f96c261351" as const;
8
8
  export const ContractAiFixturesDigest = "sha256:edac31c92e146ca1c40130f2fb5649db82e24b8cd1ac758153b90612052fddb6" as const;
9
- export const ContractGeneratedGoDigest = "sha256:34ea126e68d00e09b3a61d3b65d699b451e8840d3ea1c40f4abd5b066fc5f1a4" as const;
10
- export const ContractGeneratedTypeScriptDigest = "sha256:386ffce01eaf19ecd3ab800edf41c3288b672acf935b2a468be1edccc7980c7e" as const;
9
+ export const ContractGeneratedGoDigest = "sha256:2e8b6f09caa663a5c29f7501b0e2e70fbc6ece8918074fbb76640e6727a48f15" as const;
10
+ export const ContractGeneratedTypeScriptDigest = "sha256:e6f70790135277ed4586cbc14d411c08cb6fd6cc40b25343f4ee79eb4981ec6a" as const;
@@ -117,6 +117,10 @@ export interface EmailArrivalTrigger {
117
117
  type: 'email.arrived';
118
118
  connection: string;
119
119
  resource: 'inbox';
120
+ /**
121
+ * 0.106.0 (ADR 0049), Gmail only: whose mailbox the trigger watches. `app` is the company mailbox IT connected on the connection, with no owner and no personal consent; `user` and absent are the app owner's own mailbox, as before.
122
+ */
123
+ mode?: 'user' | 'app';
120
124
  filter?: {
121
125
  from?: string;
122
126
  subjectContains?: string;
@@ -106,9 +106,10 @@ export const IntegrationExecuteInputRules = {
106
106
  text: { kind: "string", required: true, minLength: 1, maxLength: 4000 },
107
107
  html: { kind: "string", required: false, minLength: 1, maxLength: 65536 },
108
108
  attachments: { kind: "object[]", required: false, minItems: 1, maxItems: 5, fields: {
109
- filename: { kind: "string", required: true, minLength: 1, maxLength: 255 },
110
- contentType: { kind: "string", required: true, minLength: 1, maxLength: 128 },
111
- dataBase64: { kind: "string", required: true, minLength: 1, maxLength: 2796203 },
109
+ file: { kind: "string", required: false, minLength: 1, maxLength: 1024 },
110
+ filename: { kind: "string", required: false, minLength: 1, maxLength: 255 },
111
+ contentType: { kind: "string", required: false, minLength: 1, maxLength: 128 },
112
+ dataBase64: { kind: "string", required: false, minLength: 1, maxLength: 2796203 },
112
113
  } },
113
114
  replyToMessageId: { kind: "string", required: false, minLength: 0, pattern: "^[A-Za-z0-9_-]{1,64}$" },
114
115
  replyTo: { kind: "string", required: false, minLength: 3, maxLength: 254, pattern: "^[^\\s@<>,;\"]+@[^\\s@<>,;\"]+\\.[^\\s@<>,;\"]+$" },
@@ -490,9 +491,10 @@ export const IntegrationExecuteInputRules = {
490
491
  text: { kind: "string", required: true, minLength: 1, maxLength: 4000 },
491
492
  html: { kind: "string", required: false, minLength: 1, maxLength: 65536 },
492
493
  attachments: { kind: "object[]", required: false, minItems: 1, maxItems: 5, fields: {
493
- filename: { kind: "string", required: true, minLength: 1, maxLength: 255 },
494
- contentType: { kind: "string", required: true, minLength: 1, maxLength: 128 },
495
- dataBase64: { kind: "string", required: true, minLength: 1, maxLength: 2796203 },
494
+ file: { kind: "string", required: false, minLength: 1, maxLength: 1024 },
495
+ filename: { kind: "string", required: false, minLength: 1, maxLength: 255 },
496
+ contentType: { kind: "string", required: false, minLength: 1, maxLength: 128 },
497
+ dataBase64: { kind: "string", required: false, minLength: 1, maxLength: 2796203 },
496
498
  } },
497
499
  },
498
500
  },
@@ -502,9 +504,10 @@ export const IntegrationExecuteInputRules = {
502
504
  messageId: { kind: "string", required: true, minLength: 0, pattern: "^[A-Za-z0-9._=-]{1,256}$" },
503
505
  text: { kind: "string", required: true, minLength: 1, maxLength: 4000 },
504
506
  attachments: { kind: "object[]", required: false, minItems: 1, maxItems: 5, fields: {
505
- filename: { kind: "string", required: true, minLength: 1, maxLength: 255 },
506
- contentType: { kind: "string", required: true, minLength: 1, maxLength: 128 },
507
- dataBase64: { kind: "string", required: true, minLength: 1, maxLength: 2796203 },
507
+ file: { kind: "string", required: false, minLength: 1, maxLength: 1024 },
508
+ filename: { kind: "string", required: false, minLength: 1, maxLength: 255 },
509
+ contentType: { kind: "string", required: false, minLength: 1, maxLength: 128 },
510
+ dataBase64: { kind: "string", required: false, minLength: 1, maxLength: 2796203 },
508
511
  } },
509
512
  },
510
513
  },
@@ -2,7 +2,7 @@
2
2
  // Code generated by tools/gen/gen.mjs from schema/*.json. DO NOT EDIT.
3
3
 
4
4
  /**
5
- * 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; since 0.104.0 `to` may be absent when cc or bcc names a recipient — governance refuses a send with no recipient at all), cc?, bcc?, subject 1..256 chars, text 1..4000 chars, html? <= 64 KiB, attachments? 1..5 of <= 2 MiB each as base64, replyToMessageId?, replyTo? (one address), fromName? 1..120 chars, inReplyTo? (an RFC Message-ID; not with 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). Google Workspace and Microsoft 365 (0.86.0, ADR 0032; every operation user mode; the seven personal providers run on their fixed alias, teams.* on a channel alias): calendar.calendar.list {pageToken?}; calendar.event.list {calendarId?, timeMin, timeMax, q?, pageToken?} (RFC 3339 date-times with a zone); calendar.event.read {calendarId?, eventId}; calendar.event.create {calendarId?, title 1..256, start, end, timeZone?, allDay?, description? <= 8000, location? <= 512, attendees? 1..100 emails, notify?} with a required idempotencyKey; calendar.event.update {calendarId?, eventId, title?, start?, end?, timeZone?, description?, location?, attendees?, notify?} and calendar.event.cancel {calendarId?, eventId, notify?} (both idempotent at Google, so no key); drive.file.list {folderId?, pageToken?}; drive.file.search {q, pageToken?}; drive.file.read {fileId, exportAs? text|csv|pdf} (exportAs renders a Google Docs, Sheets or Slides file; a binary file is downloaded as is); drive.file.upload {filename, contentType, dataBase64 <= 2 MiB, folderId?, conflict?} and drive.folder.create {title, folderId?} with a required idempotencyKey; docs.document.read {documentId}; docs.document.create {title, text? <= 64 KiB} with a required idempotencyKey; docs.document.append {documentId, text <= 64 KiB} and docs.text.replace {documentId, replacements 1..64 of {find 1..512, replace <= 4000, matchCase?}} (no key: the app owns the document's state); sheets.spreadsheet.read {spreadsheetId}; sheets.values.read {spreadsheetId, range} (A1 notation, 1..256); sheets.values.append {spreadsheetId, range, values, valueInput? raw|user} with a required idempotencyKey; sheets.values.update {spreadsheetId, range, values, valueInput?} and sheets.values.clear {spreadsheetId, range} (no key); sheets.spreadsheet.create {title, sheets? 1..20 titles} with a required idempotencyKey (`values` is 1..1000 rows of 1..100 cells, each a string <= 4000, a number, a boolean or null); outlook.message.list {folder?, q?, pageToken?}; outlook.message.read {messageId}; outlook.attachment.read {messageId, attachmentId}; outlook.message.send {to, cc?, bcc?, subject, text 1..4000, html?, attachments?} and outlook.message.reply {messageId, text 1..4000, attachments?} (0.95.0: existing EmailAttachment bounds; recipients remain provider-resolved) with a required idempotencyKey like gmail.message.send; outlook.event.list {start, end, pageToken?}; outlook.event.read {eventId}; outlook.event.create (as calendar.event.create without calendarId) with a required idempotencyKey; outlook.event.update (as calendar.event.update without calendarId) and outlook.event.cancel {eventId, comment? <= 4000} (no key); onedrive.file.list, onedrive.file.search, onedrive.file.read {fileId}, onedrive.file.upload and onedrive.folder.create as the drive.* operations (Graph ids, no exportAs); excel.worksheet.list {fileId, driveId?}; excel.range.read {fileId, driveId?, worksheet, address? 1..64} (no address: the used range); excel.range.update {fileId, driveId?, worksheet, address, values} (no key: a PATCH); excel.table.list {fileId, driveId?, worksheet?}; excel.row.add {fileId, driveId?, tableName, values} and excel.worksheet.add {fileId, driveId?, title} with a required idempotencyKey; teams.message.list {pageToken?}; teams.message.post {text 1..4000, html?} and teams.reply.post {messageId, text, html?} with a required idempotencyKey. An Outlook or Teams `messageId` is a Graph id (the shared field widens to ^[A-Za-z0-9._=-]{1,256}$; the Gmail and Slack operations narrow it back to ^[A-Za-z0-9._-]{1,64}$ in their then-blocks, so their rules are unchanged), and `text` widens to 64 KiB for the Docs body (every other operation that takes it narrows it back to 4000). 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, drive.file.upload or onedrive.file.upload (its 2 MiB file as base64; governance sizes it like the Gmail attachment route), an outlook.message.send or outlook.message.reply with attachments (the Gmail route), a docs.document.create or docs.document.append (its 64 KiB body; governance allows 80 KiB there, as for basecamp.message.post) and a sheets.values.append, sheets.values.update, excel.range.update or excel.row.add (its 64 KiB `values`), total deadline 10 s.
5
+ * 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; since 0.104.0 `to` may be absent when cc or bcc names a recipient — governance refuses a send with no recipient at all), cc?, bcc?, subject 1..256 chars, text 1..4000 chars, html? <= 64 KiB, attachments? 1..5, each {filename, contentType, dataBase64} (inline, 2 MiB in total) or since 0.105.0 {file, filename?, contentType?} (a private app file by path, deployed apps only; 18 MiB in total), replyToMessageId?, replyTo? (one address), fromName? 1..120 chars, inReplyTo? (an RFC Message-ID; not with 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). Google Workspace and Microsoft 365 (0.86.0, ADR 0032; every operation user mode; the seven personal providers run on their fixed alias, teams.* on a channel alias): calendar.calendar.list {pageToken?}; calendar.event.list {calendarId?, timeMin, timeMax, q?, pageToken?} (RFC 3339 date-times with a zone); calendar.event.read {calendarId?, eventId}; calendar.event.create {calendarId?, title 1..256, start, end, timeZone?, allDay?, description? <= 8000, location? <= 512, attendees? 1..100 emails, notify?} with a required idempotencyKey; calendar.event.update {calendarId?, eventId, title?, start?, end?, timeZone?, description?, location?, attendees?, notify?} and calendar.event.cancel {calendarId?, eventId, notify?} (both idempotent at Google, so no key); drive.file.list {folderId?, pageToken?}; drive.file.search {q, pageToken?}; drive.file.read {fileId, exportAs? text|csv|pdf} (exportAs renders a Google Docs, Sheets or Slides file; a binary file is downloaded as is); drive.file.upload {filename, contentType, dataBase64 <= 2 MiB, folderId?, conflict?} and drive.folder.create {title, folderId?} with a required idempotencyKey; docs.document.read {documentId}; docs.document.create {title, text? <= 64 KiB} with a required idempotencyKey; docs.document.append {documentId, text <= 64 KiB} and docs.text.replace {documentId, replacements 1..64 of {find 1..512, replace <= 4000, matchCase?}} (no key: the app owns the document's state); sheets.spreadsheet.read {spreadsheetId}; sheets.values.read {spreadsheetId, range} (A1 notation, 1..256); sheets.values.append {spreadsheetId, range, values, valueInput? raw|user} with a required idempotencyKey; sheets.values.update {spreadsheetId, range, values, valueInput?} and sheets.values.clear {spreadsheetId, range} (no key); sheets.spreadsheet.create {title, sheets? 1..20 titles} with a required idempotencyKey (`values` is 1..1000 rows of 1..100 cells, each a string <= 4000, a number, a boolean or null); outlook.message.list {folder?, q?, pageToken?}; outlook.message.read {messageId}; outlook.attachment.read {messageId, attachmentId}; outlook.message.send {to, cc?, bcc?, subject, text 1..4000, html?, attachments?} and outlook.message.reply {messageId, text 1..4000, attachments?} (0.95.0: existing EmailAttachment bounds; recipients remain provider-resolved) with a required idempotencyKey like gmail.message.send; outlook.event.list {start, end, pageToken?}; outlook.event.read {eventId}; outlook.event.create (as calendar.event.create without calendarId) with a required idempotencyKey; outlook.event.update (as calendar.event.update without calendarId) and outlook.event.cancel {eventId, comment? <= 4000} (no key); onedrive.file.list, onedrive.file.search, onedrive.file.read {fileId}, onedrive.file.upload and onedrive.folder.create as the drive.* operations (Graph ids, no exportAs); excel.worksheet.list {fileId, driveId?}; excel.range.read {fileId, driveId?, worksheet, address? 1..64} (no address: the used range); excel.range.update {fileId, driveId?, worksheet, address, values} (no key: a PATCH); excel.table.list {fileId, driveId?, worksheet?}; excel.row.add {fileId, driveId?, tableName, values} and excel.worksheet.add {fileId, driveId?, title} with a required idempotencyKey; teams.message.list {pageToken?}; teams.message.post {text 1..4000, html?} and teams.reply.post {messageId, text, html?} with a required idempotencyKey. An Outlook or Teams `messageId` is a Graph id (the shared field widens to ^[A-Za-z0-9._=-]{1,256}$; the Gmail and Slack operations narrow it back to ^[A-Za-z0-9._-]{1,64}$ in their then-blocks, so their rules are unchanged), and `text` widens to 64 KiB for the Docs body (every other operation that takes it narrows it back to 4000). 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, drive.file.upload or onedrive.file.upload (its 2 MiB file as base64; governance sizes it like the Gmail attachment route), an outlook.message.send or outlook.message.reply with attachments (the Gmail route), a docs.document.create or docs.document.append (its 64 KiB body; governance allows 80 KiB there, as for basecamp.message.post) and a sheets.values.append, sheets.values.update, excel.range.update or excel.row.add (its 64 KiB `values`), total deadline 10 s.
6
6
  */
7
7
  export type IntegrationExecuteRequest = {
8
8
  /**
@@ -10,7 +10,7 @@ export type IntegrationExecuteRequest = {
10
10
  */
11
11
  connection: string;
12
12
  /**
13
- * Closed operation set. Extending it is a contracts change. 0.102.0 (ADR 0045) makes gmail.message.send declared-mode: user sends from the signed-in person's mailbox, app sends from the company mailbox IT connected (the installation on the Gmail connection); every other gmail.* operation stays user mode. 0.86.0 (ADR 0032) adds the forty-five Google Workspace and Microsoft 365 operations, every one user mode only: on the provider's fixed alias for the signed-in person's own data, calendar.calendar.list, calendar.event.list, calendar.event.read, calendar.event.create (a receipted write: explicit action, idempotencyKey), calendar.event.update and calendar.event.cancel (idempotent at Google, no key); drive.file.list, drive.file.search, drive.file.read, drive.file.upload and drive.folder.create (receipted writes); docs.document.read, docs.document.create (receipted), docs.document.append and docs.text.replace (no key); sheets.spreadsheet.read, sheets.values.read, sheets.values.append (receipted), sheets.values.update and sheets.values.clear (no key), sheets.spreadsheet.create (receipted); outlook.message.list, outlook.message.read, outlook.attachment.read, outlook.message.send and outlook.message.reply (receipted sends, as gmail.message.send), outlook.event.list, outlook.event.read, outlook.event.create (receipted), outlook.event.update and outlook.event.cancel (no key); onedrive.file.list, onedrive.file.search, onedrive.file.read, onedrive.file.upload and onedrive.folder.create (receipted); excel.worksheet.list, excel.range.read, excel.range.update (no key), excel.table.list, excel.row.add and excel.worksheet.add (receipted); and on a Teams channel alias (a key of the connection's `channels` map) teams.message.list, teams.message.post and teams.reply.post (receipted). 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. 0.82.0 (ADR 0028) adds phone.sms.list (app mode only, a number alias from a phone connection's `channels` map: the SMS received on that number since a time) and warehouse.table.append (app mode only on a database connection, a receipted write with idempotencyKey: rows appended to a view alias whose mapping says `access: append`). 0.83.0 (ADR 0029) adds phone.number.get (app mode only, read-only, no input, on a number alias from a phone connection's `channels` map: the E.164 number that alias maps to, so the app — or a computer-use agent it drives — can type it into a portal that signs in by mobile number). 0.85.0 (ADR 0031) raises warehouse.table.append's `rows` from 1..500 to 1..5000 records (the byte cap stays governance's, now 8 MiB), so a downloaded report of a few thousand rows lands in one call.
13
+ * Closed operation set. Extending it is a contracts change. 0.106.0 (ADR 0049) makes the four Gmail reads (gmail.thread.list, gmail.thread.read, gmail.message.read, gmail.attachment.read) declared-mode like the send: user reads the signed-in person's mailbox, app reads the company mailbox IT connected, when its sign-in granted reading. 0.102.0 (ADR 0045) makes gmail.message.send declared-mode: user sends from the signed-in person's mailbox, app sends from the company mailbox IT connected (the installation on the Gmail connection); every other gmail.* operation stays user mode. 0.86.0 (ADR 0032) adds the forty-five Google Workspace and Microsoft 365 operations, every one user mode only: on the provider's fixed alias for the signed-in person's own data, calendar.calendar.list, calendar.event.list, calendar.event.read, calendar.event.create (a receipted write: explicit action, idempotencyKey), calendar.event.update and calendar.event.cancel (idempotent at Google, no key); drive.file.list, drive.file.search, drive.file.read, drive.file.upload and drive.folder.create (receipted writes); docs.document.read, docs.document.create (receipted), docs.document.append and docs.text.replace (no key); sheets.spreadsheet.read, sheets.values.read, sheets.values.append (receipted), sheets.values.update and sheets.values.clear (no key), sheets.spreadsheet.create (receipted); outlook.message.list, outlook.message.read, outlook.attachment.read, outlook.message.send and outlook.message.reply (receipted sends, as gmail.message.send), outlook.event.list, outlook.event.read, outlook.event.create (receipted), outlook.event.update and outlook.event.cancel (no key); onedrive.file.list, onedrive.file.search, onedrive.file.read, onedrive.file.upload and onedrive.folder.create (receipted); excel.worksheet.list, excel.range.read, excel.range.update (no key), excel.table.list, excel.row.add and excel.worksheet.add (receipted); and on a Teams channel alias (a key of the connection's `channels` map) teams.message.list, teams.message.post and teams.reply.post (receipted). 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. 0.82.0 (ADR 0028) adds phone.sms.list (app mode only, a number alias from a phone connection's `channels` map: the SMS received on that number since a time) and warehouse.table.append (app mode only on a database connection, a receipted write with idempotencyKey: rows appended to a view alias whose mapping says `access: append`). 0.83.0 (ADR 0029) adds phone.number.get (app mode only, read-only, no input, on a number alias from a phone connection's `channels` map: the E.164 number that alias maps to, so the app — or a computer-use agent it drives — can type it into a portal that signs in by mobile number). 0.85.0 (ADR 0031) raises warehouse.table.append's `rows` from 1..500 to 1..5000 records (the byte cap stays governance's, now 8 MiB), so a downloaded report of a few thousand rows lands in one call.
14
14
  */
15
15
  operation:
16
16
  | 'slack.channel.history'
@@ -197,7 +197,7 @@ export type IntegrationExecuteRequest = {
197
197
  */
198
198
  html?: string;
199
199
  /**
200
- * gmail.message.send (0.71.0): files to attach, 1..5, each at most 2 MiB (2,796,203 base64 characters). outlook.message.send (0.86.0): the same, 1..5.
200
+ * gmail.message.send (0.71.0): files to attach, 1..5; inline ones (`dataBase64`) total at most 2 MiB, and since 0.105.0 one may instead name a private app file (`file`), 18 MiB in total. outlook.message.send (0.86.0): inline files only, 1..5.
201
201
  *
202
202
  * @minItems 1
203
203
  * @maxItems 5
@@ -510,7 +510,7 @@ export type IntegrationExecuteRequest = {
510
510
  */
511
511
  idempotencyKey?: string;
512
512
  /**
513
- * The declared-mode operations only (slack.message.post since 0.55.0; slack.channel.history, slack.message.update and slack.message.delete since 0.71.0; gmail.message.send since 0.102.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; for gmail.message.send, the company mailbox IT connected) or user (the signed-in person, after their consent; no name, no footer; for gmail.message.send, their own mailbox). 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. MCP accepts explicit user only; shared app mode is deferred.
513
+ * The declared-mode operations only (slack.message.post since 0.55.0; slack.channel.history, slack.message.update and slack.message.delete since 0.71.0; gmail.message.send since 0.102.0; gmail.thread.list, gmail.thread.read, gmail.message.read and gmail.attachment.read since 0.106.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; for gmail.message.send, the company mailbox IT connected) or user (the signed-in person, after their consent; no name, no footer; for gmail.message.send, their own mailbox). A Gmail read in app mode reads the company mailbox; in user mode, and with no mode when the app holds only that grant, the signed-in person's own. 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. MCP accepts explicit user only; shared app mode is deferred.
514
514
  */
515
515
  mode?: 'app' | 'user';
516
516
  /**
@@ -561,21 +561,25 @@ export type WarehouseAggregateFn = 'count' | 'sum' | 'avg' | 'min' | 'max';
561
561
  export type WarehouseOrderDirection = 'asc' | 'desc';
562
562
 
563
563
  /**
564
- * One file attached to a gmail.message.send (0.71.0) or an outlook.message.send (0.86.0), carried inline as base64. At most 2 MiB of bytes, which is 2,796,203 base64 characters.
564
+ * One file attached to a gmail.message.send (0.71.0) or an outlook.message.send (0.86.0). Either carried inline as base64 — `filename`, `contentType` and `dataBase64`, at most 2 MiB of bytes, which is 2,796,203 base64 characters — or, on a gmail.message.send from a deployed app (0.105.0), named by `file`: the path of a private file of the app (isomorph.files), which the app's gateway checks the caller may read and hands to governance as a short-lived link, so no file bytes ride the request; `filename` and `contentType` then default to the file's own. Exactly one of `dataBase64` and `file`: a cross-field rule governance enforces (no field is required here). The Outlook sends take the inline form only.
565
565
  */
566
566
  export interface EmailAttachment {
567
+ /**
568
+ * gmail.message.send (0.105.0): the path of one of the app's private files, as isomorph.files names it (e.g. letters/offer.pdf). The inline and referenced attachments of one email total at most 18 MiB decoded.
569
+ */
570
+ file?: string;
567
571
  /**
568
572
  * The name the recipient sees.
569
573
  */
570
- filename: string;
574
+ filename?: string;
571
575
  /**
572
576
  * The MIME type, e.g. application/pdf.
573
577
  */
574
- contentType: string;
578
+ contentType?: string;
575
579
  /**
576
580
  * The file bytes, standard base64.
577
581
  */
578
- dataBase64: string;
582
+ dataBase64?: string;
579
583
  }
580
584
  /**
581
585
  * 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.
@@ -64,28 +64,31 @@ export const IntegrationOperations = {
64
64
  "gmail.thread.list": {
65
65
  "kind": "saas",
66
66
  "identities": [
67
- "user"
67
+ "user",
68
+ "app"
68
69
  ],
69
70
  "readOnly": true,
70
- "declaredMode": false,
71
+ "declaredMode": true,
71
72
  "idempotencyKey": false
72
73
  },
73
74
  "gmail.thread.read": {
74
75
  "kind": "saas",
75
76
  "identities": [
76
- "user"
77
+ "user",
78
+ "app"
77
79
  ],
78
80
  "readOnly": true,
79
- "declaredMode": false,
81
+ "declaredMode": true,
80
82
  "idempotencyKey": false
81
83
  },
82
84
  "gmail.message.read": {
83
85
  "kind": "saas",
84
86
  "identities": [
85
- "user"
87
+ "user",
88
+ "app"
86
89
  ],
87
90
  "readOnly": true,
88
- "declaredMode": false,
91
+ "declaredMode": true,
89
92
  "idempotencyKey": false
90
93
  },
91
94
  "gmail.message.send": {
@@ -101,10 +104,11 @@ export const IntegrationOperations = {
101
104
  "gmail.attachment.read": {
102
105
  "kind": "saas",
103
106
  "identities": [
104
- "user"
107
+ "user",
108
+ "app"
105
109
  ],
106
110
  "readOnly": true,
107
- "declaredMode": false,
111
+ "declaredMode": true,
108
112
  "idempotencyKey": false
109
113
  },
110
114
  "basecamp.project.list": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fourier-labs/harbour-contracts",
3
- "version": "0.104.0",
3
+ "version": "0.106.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,
@@ -151,7 +151,7 @@
151
151
  "zohobooks.creditnote.read",
152
152
  "mcp.tool.call"
153
153
  ],
154
- "description": "Closed operation set. Extending it is a contracts change. 0.102.0 (ADR 0045) makes gmail.message.send declared-mode: user sends from the signed-in person's mailbox, app sends from the company mailbox IT connected (the installation on the Gmail connection); every other gmail.* operation stays user mode. 0.86.0 (ADR 0032) adds the forty-five Google Workspace and Microsoft 365 operations, every one user mode only: on the provider's fixed alias for the signed-in person's own data, calendar.calendar.list, calendar.event.list, calendar.event.read, calendar.event.create (a receipted write: explicit action, idempotencyKey), calendar.event.update and calendar.event.cancel (idempotent at Google, no key); drive.file.list, drive.file.search, drive.file.read, drive.file.upload and drive.folder.create (receipted writes); docs.document.read, docs.document.create (receipted), docs.document.append and docs.text.replace (no key); sheets.spreadsheet.read, sheets.values.read, sheets.values.append (receipted), sheets.values.update and sheets.values.clear (no key), sheets.spreadsheet.create (receipted); outlook.message.list, outlook.message.read, outlook.attachment.read, outlook.message.send and outlook.message.reply (receipted sends, as gmail.message.send), outlook.event.list, outlook.event.read, outlook.event.create (receipted), outlook.event.update and outlook.event.cancel (no key); onedrive.file.list, onedrive.file.search, onedrive.file.read, onedrive.file.upload and onedrive.folder.create (receipted); excel.worksheet.list, excel.range.read, excel.range.update (no key), excel.table.list, excel.row.add and excel.worksheet.add (receipted); and on a Teams channel alias (a key of the connection's `channels` map) teams.message.list, teams.message.post and teams.reply.post (receipted). 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. 0.82.0 (ADR 0028) adds phone.sms.list (app mode only, a number alias from a phone connection's `channels` map: the SMS received on that number since a time) and warehouse.table.append (app mode only on a database connection, a receipted write with idempotencyKey: rows appended to a view alias whose mapping says `access: append`). 0.83.0 (ADR 0029) adds phone.number.get (app mode only, read-only, no input, on a number alias from a phone connection's `channels` map: the E.164 number that alias maps to, so the app — or a computer-use agent it drives — can type it into a portal that signs in by mobile number). 0.85.0 (ADR 0031) raises warehouse.table.append's `rows` from 1..500 to 1..5000 records (the byte cap stays governance's, now 8 MiB), so a downloaded report of a few thousand rows lands in one call."
154
+ "description": "Closed operation set. Extending it is a contracts change. 0.106.0 (ADR 0049) makes the four Gmail reads (gmail.thread.list, gmail.thread.read, gmail.message.read, gmail.attachment.read) declared-mode like the send: user reads the signed-in person's mailbox, app reads the company mailbox IT connected, when its sign-in granted reading. 0.102.0 (ADR 0045) makes gmail.message.send declared-mode: user sends from the signed-in person's mailbox, app sends from the company mailbox IT connected (the installation on the Gmail connection); every other gmail.* operation stays user mode. 0.86.0 (ADR 0032) adds the forty-five Google Workspace and Microsoft 365 operations, every one user mode only: on the provider's fixed alias for the signed-in person's own data, calendar.calendar.list, calendar.event.list, calendar.event.read, calendar.event.create (a receipted write: explicit action, idempotencyKey), calendar.event.update and calendar.event.cancel (idempotent at Google, no key); drive.file.list, drive.file.search, drive.file.read, drive.file.upload and drive.folder.create (receipted writes); docs.document.read, docs.document.create (receipted), docs.document.append and docs.text.replace (no key); sheets.spreadsheet.read, sheets.values.read, sheets.values.append (receipted), sheets.values.update and sheets.values.clear (no key), sheets.spreadsheet.create (receipted); outlook.message.list, outlook.message.read, outlook.attachment.read, outlook.message.send and outlook.message.reply (receipted sends, as gmail.message.send), outlook.event.list, outlook.event.read, outlook.event.create (receipted), outlook.event.update and outlook.event.cancel (no key); onedrive.file.list, onedrive.file.search, onedrive.file.read, onedrive.file.upload and onedrive.folder.create (receipted); excel.worksheet.list, excel.range.read, excel.range.update (no key), excel.table.list, excel.row.add and excel.worksheet.add (receipted); and on a Teams channel alias (a key of the connection's `channels` map) teams.message.list, teams.message.post and teams.reply.post (receipted). 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. 0.82.0 (ADR 0028) adds phone.sms.list (app mode only, a number alias from a phone connection's `channels` map: the SMS received on that number since a time) and warehouse.table.append (app mode only on a database connection, a receipted write with idempotencyKey: rows appended to a view alias whose mapping says `access: append`). 0.83.0 (ADR 0029) adds phone.number.get (app mode only, read-only, no input, on a number alias from a phone connection's `channels` map: the E.164 number that alias maps to, so the app — or a computer-use agent it drives — can type it into a portal that signs in by mobile number). 0.85.0 (ADR 0031) raises warehouse.table.append's `rows` from 1..500 to 1..5000 records (the byte cap stays governance's, now 8 MiB), so a downloaded report of a few thousand rows lands in one call."
155
155
  },
156
156
  "IntegrationIdentityMode": {
157
157
  "type": "string",
@@ -250,7 +250,7 @@
250
250
  "identity",
251
251
  "resources"
252
252
  ],
253
- "description": "Every gmail.* operation except gmail.message.send, declared-mode since 0.102.0 (resource: the fixed `inbox` alias), since 0.76.0 every basecamp.* operation (resource: a project alias from the connection's `channels` map, or the fixed `account` alias for basecamp.project.list), since 0.78.0 every sharepoint.* operation (resource: a site alias from the connection's `channels` map) and since 0.86.0 every calendar.*, drive.*, docs.*, sheets.*, outlook.*, onedrive.*, excel.* and teams.* operation (resource: the provider's fixed alias for the person's own data, or for teams.* a channel alias from the connection's `channels` map): user mode only — the app acts as the signed-in person after their own consent.",
253
+ "description": "Since 0.106.0 no gmail.* operation (the send has been declared-mode since 0.102.0, the four reads since 0.106.0); since 0.76.0 every basecamp.* operation (resource: a project alias from the connection's `channels` map, or the fixed `account` alias for basecamp.project.list), since 0.78.0 every sharepoint.* operation (resource: a site alias from the connection's `channels` map) and since 0.86.0 every calendar.*, drive.*, docs.*, sheets.*, outlook.*, onedrive.*, excel.* and teams.* operation (resource: the provider's fixed alias for the person's own data, or for teams.* a channel alias from the connection's `channels` map): user mode only — the app acts as the signed-in person after their own consent.",
254
254
  "properties": {
255
255
  "identity": {
256
256
  "type": "string",
@@ -268,7 +268,7 @@
268
268
  "identity",
269
269
  "resources"
270
270
  ],
271
- "description": "slack.message.post, slack.channel.history, slack.message.update, slack.message.delete: the app declares the mode — app (acts as the company's Slack app) or user (acts as the signed-in person). Since 0.102.0 (ADR 0045) gmail.message.send too, on the fixed `inbox` alias: user sends from the signed-in person's own mailbox after their consent; app sends from the company mailbox IT connected (the `installation` on the Gmail connection).",
271
+ "description": "slack.message.post, slack.channel.history, slack.message.update, slack.message.delete: the app declares the mode — app (acts as the company's Slack app) or user (acts as the signed-in person). Since 0.106.0 (ADR 0049) the four Gmail reads too, on the fixed `inbox` alias: user reads the signed-in person's own mailbox, app reads the company mailbox IT connected. Since 0.102.0 (ADR 0045) gmail.message.send too, on the fixed `inbox` alias: user sends from the signed-in person's own mailbox after their consent; app sends from the company mailbox IT connected (the `installation` on the Gmail connection).",
272
272
  "properties": {
273
273
  "identity": {
274
274
  "$ref": "#/definitions/IntegrationIdentityMode"
@@ -360,19 +360,19 @@
360
360
  "$ref": "#/definitions/AppModeChannelOperation"
361
361
  },
362
362
  "gmail.thread.list": {
363
- "$ref": "#/definitions/UserModeChannelOperation"
363
+ "$ref": "#/definitions/DeclaredModeChannelOperation"
364
364
  },
365
365
  "gmail.thread.read": {
366
- "$ref": "#/definitions/UserModeChannelOperation"
366
+ "$ref": "#/definitions/DeclaredModeChannelOperation"
367
367
  },
368
368
  "gmail.message.read": {
369
- "$ref": "#/definitions/UserModeChannelOperation"
369
+ "$ref": "#/definitions/DeclaredModeChannelOperation"
370
370
  },
371
371
  "gmail.message.send": {
372
372
  "$ref": "#/definitions/DeclaredModeChannelOperation"
373
373
  },
374
374
  "gmail.attachment.read": {
375
- "$ref": "#/definitions/UserModeChannelOperation"
375
+ "$ref": "#/definitions/DeclaredModeChannelOperation"
376
376
  },
377
377
  "basecamp.project.list": {
378
378
  "$ref": "#/definitions/UserModeChannelOperation"
@@ -448,6 +448,13 @@
448
448
  "resource": {
449
449
  "const": "inbox"
450
450
  },
451
+ "mode": {
452
+ "enum": [
453
+ "user",
454
+ "app"
455
+ ],
456
+ "description": "0.106.0 (ADR 0049), Gmail only: whose mailbox the trigger watches. `app` is the company mailbox IT connected on the connection, with no owner and no personal consent; `user` and absent are the app owner's own mailbox, as before."
457
+ },
451
458
  "filter": {
452
459
  "type": "object",
453
460
  "additionalProperties": false,
@@ -2,7 +2,7 @@
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) \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; since 0.104.0 `to` may be absent when cc or bcc names a recipient \u2014 governance refuses a send with no recipient at all), cc?, bcc?, subject 1..256 chars, text 1..4000 chars, html? <= 64 KiB, attachments? 1..5 of <= 2 MiB each as base64, replyToMessageId?, replyTo? (one address), fromName? 1..120 chars, inReplyTo? (an RFC Message-ID; not with 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). 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 \u2014 GraphId, 1..256 characters \u2014 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). Google Workspace and Microsoft 365 (0.86.0, ADR 0032; every operation user mode; the seven personal providers run on their fixed alias, teams.* on a channel alias): calendar.calendar.list {pageToken?}; calendar.event.list {calendarId?, timeMin, timeMax, q?, pageToken?} (RFC 3339 date-times with a zone); calendar.event.read {calendarId?, eventId}; calendar.event.create {calendarId?, title 1..256, start, end, timeZone?, allDay?, description? <= 8000, location? <= 512, attendees? 1..100 emails, notify?} with a required idempotencyKey; calendar.event.update {calendarId?, eventId, title?, start?, end?, timeZone?, description?, location?, attendees?, notify?} and calendar.event.cancel {calendarId?, eventId, notify?} (both idempotent at Google, so no key); drive.file.list {folderId?, pageToken?}; drive.file.search {q, pageToken?}; drive.file.read {fileId, exportAs? text|csv|pdf} (exportAs renders a Google Docs, Sheets or Slides file; a binary file is downloaded as is); drive.file.upload {filename, contentType, dataBase64 <= 2 MiB, folderId?, conflict?} and drive.folder.create {title, folderId?} with a required idempotencyKey; docs.document.read {documentId}; docs.document.create {title, text? <= 64 KiB} with a required idempotencyKey; docs.document.append {documentId, text <= 64 KiB} and docs.text.replace {documentId, replacements 1..64 of {find 1..512, replace <= 4000, matchCase?}} (no key: the app owns the document's state); sheets.spreadsheet.read {spreadsheetId}; sheets.values.read {spreadsheetId, range} (A1 notation, 1..256); sheets.values.append {spreadsheetId, range, values, valueInput? raw|user} with a required idempotencyKey; sheets.values.update {spreadsheetId, range, values, valueInput?} and sheets.values.clear {spreadsheetId, range} (no key); sheets.spreadsheet.create {title, sheets? 1..20 titles} with a required idempotencyKey (`values` is 1..1000 rows of 1..100 cells, each a string <= 4000, a number, a boolean or null); outlook.message.list {folder?, q?, pageToken?}; outlook.message.read {messageId}; outlook.attachment.read {messageId, attachmentId}; outlook.message.send {to, cc?, bcc?, subject, text 1..4000, html?, attachments?} and outlook.message.reply {messageId, text 1..4000, attachments?} (0.95.0: existing EmailAttachment bounds; recipients remain provider-resolved) with a required idempotencyKey like gmail.message.send; outlook.event.list {start, end, pageToken?}; outlook.event.read {eventId}; outlook.event.create (as calendar.event.create without calendarId) with a required idempotencyKey; outlook.event.update (as calendar.event.update without calendarId) and outlook.event.cancel {eventId, comment? <= 4000} (no key); onedrive.file.list, onedrive.file.search, onedrive.file.read {fileId}, onedrive.file.upload and onedrive.folder.create as the drive.* operations (Graph ids, no exportAs); excel.worksheet.list {fileId, driveId?}; excel.range.read {fileId, driveId?, worksheet, address? 1..64} (no address: the used range); excel.range.update {fileId, driveId?, worksheet, address, values} (no key: a PATCH); excel.table.list {fileId, driveId?, worksheet?}; excel.row.add {fileId, driveId?, tableName, values} and excel.worksheet.add {fileId, driveId?, title} with a required idempotencyKey; teams.message.list {pageToken?}; teams.message.post {text 1..4000, html?} and teams.reply.post {messageId, text, html?} with a required idempotencyKey. An Outlook or Teams `messageId` is a Graph id (the shared field widens to ^[A-Za-z0-9._=-]{1,256}$; the Gmail and Slack operations narrow it back to ^[A-Za-z0-9._-]{1,64}$ in their then-blocks, so their rules are unchanged), and `text` widens to 64 KiB for the Docs body (every other operation that takes it narrows it back to 4000). 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, drive.file.upload or onedrive.file.upload (its 2 MiB file as base64; governance sizes it like the Gmail attachment route), an outlook.message.send or outlook.message.reply with attachments (the Gmail route), a docs.document.create or docs.document.append (its 64 KiB body; governance allows 80 KiB there, as for basecamp.message.post) and a sheets.values.append, sheets.values.update, excel.range.update or excel.row.add (its 64 KiB `values`), 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; since 0.104.0 `to` may be absent when cc or bcc names a recipient \u2014 governance refuses a send with no recipient at all), cc?, bcc?, subject 1..256 chars, text 1..4000 chars, html? <= 64 KiB, attachments? 1..5, each {filename, contentType, dataBase64} (inline, 2 MiB in total) or since 0.105.0 {file, filename?, contentType?} (a private app file by path, deployed apps only; 18 MiB in total), replyToMessageId?, replyTo? (one address), fromName? 1..120 chars, inReplyTo? (an RFC Message-ID; not with 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). 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 \u2014 GraphId, 1..256 characters \u2014 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). Google Workspace and Microsoft 365 (0.86.0, ADR 0032; every operation user mode; the seven personal providers run on their fixed alias, teams.* on a channel alias): calendar.calendar.list {pageToken?}; calendar.event.list {calendarId?, timeMin, timeMax, q?, pageToken?} (RFC 3339 date-times with a zone); calendar.event.read {calendarId?, eventId}; calendar.event.create {calendarId?, title 1..256, start, end, timeZone?, allDay?, description? <= 8000, location? <= 512, attendees? 1..100 emails, notify?} with a required idempotencyKey; calendar.event.update {calendarId?, eventId, title?, start?, end?, timeZone?, description?, location?, attendees?, notify?} and calendar.event.cancel {calendarId?, eventId, notify?} (both idempotent at Google, so no key); drive.file.list {folderId?, pageToken?}; drive.file.search {q, pageToken?}; drive.file.read {fileId, exportAs? text|csv|pdf} (exportAs renders a Google Docs, Sheets or Slides file; a binary file is downloaded as is); drive.file.upload {filename, contentType, dataBase64 <= 2 MiB, folderId?, conflict?} and drive.folder.create {title, folderId?} with a required idempotencyKey; docs.document.read {documentId}; docs.document.create {title, text? <= 64 KiB} with a required idempotencyKey; docs.document.append {documentId, text <= 64 KiB} and docs.text.replace {documentId, replacements 1..64 of {find 1..512, replace <= 4000, matchCase?}} (no key: the app owns the document's state); sheets.spreadsheet.read {spreadsheetId}; sheets.values.read {spreadsheetId, range} (A1 notation, 1..256); sheets.values.append {spreadsheetId, range, values, valueInput? raw|user} with a required idempotencyKey; sheets.values.update {spreadsheetId, range, values, valueInput?} and sheets.values.clear {spreadsheetId, range} (no key); sheets.spreadsheet.create {title, sheets? 1..20 titles} with a required idempotencyKey (`values` is 1..1000 rows of 1..100 cells, each a string <= 4000, a number, a boolean or null); outlook.message.list {folder?, q?, pageToken?}; outlook.message.read {messageId}; outlook.attachment.read {messageId, attachmentId}; outlook.message.send {to, cc?, bcc?, subject, text 1..4000, html?, attachments?} and outlook.message.reply {messageId, text 1..4000, attachments?} (0.95.0: existing EmailAttachment bounds; recipients remain provider-resolved) with a required idempotencyKey like gmail.message.send; outlook.event.list {start, end, pageToken?}; outlook.event.read {eventId}; outlook.event.create (as calendar.event.create without calendarId) with a required idempotencyKey; outlook.event.update (as calendar.event.update without calendarId) and outlook.event.cancel {eventId, comment? <= 4000} (no key); onedrive.file.list, onedrive.file.search, onedrive.file.read {fileId}, onedrive.file.upload and onedrive.folder.create as the drive.* operations (Graph ids, no exportAs); excel.worksheet.list {fileId, driveId?}; excel.range.read {fileId, driveId?, worksheet, address? 1..64} (no address: the used range); excel.range.update {fileId, driveId?, worksheet, address, values} (no key: a PATCH); excel.table.list {fileId, driveId?, worksheet?}; excel.row.add {fileId, driveId?, tableName, values} and excel.worksheet.add {fileId, driveId?, title} with a required idempotencyKey; teams.message.list {pageToken?}; teams.message.post {text 1..4000, html?} and teams.reply.post {messageId, text, html?} with a required idempotencyKey. An Outlook or Teams `messageId` is a Graph id (the shared field widens to ^[A-Za-z0-9._=-]{1,256}$; the Gmail and Slack operations narrow it back to ^[A-Za-z0-9._-]{1,64}$ in their then-blocks, so their rules are unchanged), and `text` widens to 64 KiB for the Docs body (every other operation that takes it narrows it back to 4000). 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, drive.file.upload or onedrive.file.upload (its 2 MiB file as base64; governance sizes it like the Gmail attachment route), an outlook.message.send or outlook.message.reply with attachments (the Gmail route), a docs.document.create or docs.document.append (its 64 KiB body; governance allows 80 KiB there, as for basecamp.message.post) and a sheets.values.append, sheets.values.update, excel.range.update or excel.row.add (its 64 KiB `values`), total deadline 10 s.",
6
6
  "type": "object",
7
7
  "additionalProperties": false,
8
8
  "required": [
@@ -156,7 +156,7 @@
156
156
  "items": {
157
157
  "$ref": "#/definitions/EmailAttachment"
158
158
  },
159
- "description": "gmail.message.send (0.71.0): files to attach, 1..5, each at most 2 MiB (2,796,203 base64 characters). outlook.message.send (0.86.0): the same, 1..5."
159
+ "description": "gmail.message.send (0.71.0): files to attach, 1..5; inline ones (`dataBase64`) total at most 2 MiB, and since 0.105.0 one may instead name a private app file (`file`), 18 MiB in total. outlook.message.send (0.86.0): inline files only, 1..5."
160
160
  },
161
161
  "todolistId": {
162
162
  "$ref": "app-integrations-declaration.schema.json#/definitions/BasecampId",
@@ -694,7 +694,7 @@
694
694
  },
695
695
  "mode": {
696
696
  "$ref": "app-integrations-declaration.schema.json#/definitions/IntegrationIdentityMode",
697
- "description": "The declared-mode operations only (slack.message.post since 0.55.0; slack.channel.history, slack.message.update and slack.message.delete since 0.71.0; gmail.message.send since 0.102.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; for gmail.message.send, the company mailbox IT connected) or user (the signed-in person, after their consent; no name, no footer; for gmail.message.send, their own mailbox). 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. MCP accepts explicit user only; shared app mode is deferred."
697
+ "description": "The declared-mode operations only (slack.message.post since 0.55.0; slack.channel.history, slack.message.update and slack.message.delete since 0.71.0; gmail.message.send since 0.102.0; gmail.thread.list, gmail.thread.read, gmail.message.read and gmail.attachment.read since 0.106.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; for gmail.message.send, the company mailbox IT connected) or user (the signed-in person, after their consent; no name, no footer; for gmail.message.send, their own mailbox). A Gmail read in app mode reads the company mailbox; in user mode, and with no mode when the app holds only that grant, the signed-in person's own. 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. MCP accepts explicit user only; shared app mode is deferred."
698
698
  },
699
699
  "sampleRows": {
700
700
  "type": "array",
@@ -1262,13 +1262,13 @@
1262
1262
  },
1263
1263
  "then": {
1264
1264
  "x-read-only": true,
1265
+ "x-primary-identity": "user",
1265
1266
  "not": {
1266
1267
  "required": [
1267
1268
  "idempotencyKey"
1268
1269
  ]
1269
1270
  },
1270
1271
  "properties": {
1271
- "mode": false,
1272
1272
  "input": {
1273
1273
  "propertyNames": {
1274
1274
  "enum": [
@@ -1301,13 +1301,13 @@
1301
1301
  },
1302
1302
  "then": {
1303
1303
  "x-read-only": true,
1304
+ "x-primary-identity": "user",
1304
1305
  "not": {
1305
1306
  "required": [
1306
1307
  "idempotencyKey"
1307
1308
  ]
1308
1309
  },
1309
1310
  "properties": {
1310
- "mode": false,
1311
1311
  "input": {
1312
1312
  "propertyNames": {
1313
1313
  "enum": [
@@ -1331,13 +1331,13 @@
1331
1331
  },
1332
1332
  "then": {
1333
1333
  "x-read-only": true,
1334
+ "x-primary-identity": "user",
1334
1335
  "not": {
1335
1336
  "required": [
1336
1337
  "idempotencyKey"
1337
1338
  ]
1338
1339
  },
1339
1340
  "properties": {
1340
- "mode": false,
1341
1341
  "input": {
1342
1342
  "propertyNames": {
1343
1343
  "enum": [
@@ -1366,13 +1366,13 @@
1366
1366
  },
1367
1367
  "then": {
1368
1368
  "x-read-only": true,
1369
+ "x-primary-identity": "user",
1369
1370
  "not": {
1370
1371
  "required": [
1371
1372
  "idempotencyKey"
1372
1373
  ]
1373
1374
  },
1374
1375
  "properties": {
1375
- "mode": false,
1376
1376
  "input": {
1377
1377
  "propertyNames": {
1378
1378
  "enum": [
@@ -4283,13 +4283,14 @@
4283
4283
  "EmailAttachment": {
4284
4284
  "type": "object",
4285
4285
  "additionalProperties": false,
4286
- "required": [
4287
- "filename",
4288
- "contentType",
4289
- "dataBase64"
4290
- ],
4291
- "description": "One file attached to a gmail.message.send (0.71.0) or an outlook.message.send (0.86.0), carried inline as base64. At most 2 MiB of bytes, which is 2,796,203 base64 characters.",
4286
+ "description": "One file attached to a gmail.message.send (0.71.0) or an outlook.message.send (0.86.0). Either carried inline as base64 \u2014 `filename`, `contentType` and `dataBase64`, at most 2 MiB of bytes, which is 2,796,203 base64 characters \u2014 or, on a gmail.message.send from a deployed app (0.105.0), named by `file`: the path of a private file of the app (isomorph.files), which the app's gateway checks the caller may read and hands to governance as a short-lived link, so no file bytes ride the request; `filename` and `contentType` then default to the file's own. Exactly one of `dataBase64` and `file`: a cross-field rule governance enforces (no field is required here). The Outlook sends take the inline form only.",
4292
4287
  "properties": {
4288
+ "file": {
4289
+ "type": "string",
4290
+ "minLength": 1,
4291
+ "maxLength": 1024,
4292
+ "description": "gmail.message.send (0.105.0): the path of one of the app's private files, as isomorph.files names it (e.g. letters/offer.pdf). The inline and referenced attachments of one email total at most 18 MiB decoded."
4293
+ },
4293
4294
  "filename": {
4294
4295
  "type": "string",
4295
4296
  "minLength": 1,