@fourier-labs/harbour-contracts 0.54.0 → 0.56.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.
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "schema": "harbour.contract-release/1.0",
3
- "repositoryVersion": "0.54.0.0",
4
- "publishVersion": "0.54.0",
5
- "publishTag": "v0.54.0",
6
- "schemasDigest": "sha256:fc7bd2680afba97f97aa634ea1d75d93acb608268e507b36f225bcdd29d14d62",
3
+ "repositoryVersion": "0.56.0.0",
4
+ "publishVersion": "0.56.0",
5
+ "publishTag": "v0.56.0",
6
+ "schemasDigest": "sha256:8f17ac7494b6ffc21a69e9cac4bca42743aad4b2a8f0b19aff958ed5dfbda3e0",
7
7
  "aiFixturesDigest": "sha256:edac31c92e146ca1c40130f2fb5649db82e24b8cd1ac758153b90612052fddb6",
8
- "generatedGoDigest": "sha256:4a5ac1dfae12d5dab89db517aacf83ffb499b88140f56b428a05dd4a522f9a9e",
9
- "generatedTypeScriptDigest": "sha256:b6dd57fd1527d211f7ab9efbf374058251e21dade6bfdb0122053c720a1016cc",
8
+ "generatedGoDigest": "sha256:cb58c33f2f08ae7b44afaa5cada2edb69392b8a2309f16de378bc9ba428e9116",
9
+ "generatedTypeScriptDigest": "sha256:64a81d10b02820ed26c73046dca7ed9776a6f1277c2a3e954a36c01e1a30d866",
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:5d43c31719c93c9c8235b6c03dbe55a42ebdb2efb78225ad14222bd1086dcf6a",
34
+ "schema/app-integrations-declaration.schema.json": "sha256:93bfab77064f0ff912c8c51ae53da421f727fd3c8614d903f505da16f74a51ad",
35
35
  "schema/artifact-contract.schema.json": "sha256:8047b461a1c6d07bbe867a259c57bdf5cc0fdf282cf842ec7b683a9e93713b10",
36
36
  "schema/binding-classification-subject.schema.json": "sha256:a8825ce652e7e00a18759c4249bd5bcd0cae552dd58440513f867e87f685133c",
37
37
  "schema/builder-input-proposal.schema.json": "sha256:fe85cffa6d76b1e1effe1f5d75525f8fe0e1eaa3be4a304e491aa23ad72c0e72",
@@ -51,10 +51,10 @@
51
51
  "schema/error-category.enum.json": "sha256:2268b8a00dfa1f1ab6db7650c117028247fca980d4a23aa5969ce30b76692be9",
52
52
  "schema/error-envelope.schema.json": "sha256:75dccda7dd16fdf8435deda53fc7dbcdc109b65114617a17f11763010c656f90",
53
53
  "schema/integration-connect-result.schema.json": "sha256:3f254b67ef739dc1fa395bdc8c4dc93359b16d481dba2a2d777aa5b6f34b7af8",
54
- "schema/integration-error-details.schema.json": "sha256:09cebaddb7f88e63cbebaa1cada5f91057ae4dc4370e973ef38f7f11c6323284",
55
- "schema/integration-execute-request.schema.json": "sha256:af2b60a623f397bbd82f825e67baaf07f0c67e74c812245a58bb3e87d8204c8e",
56
- "schema/integration-execute-result.schema.json": "sha256:766146779c01ee1dbdd2161c5dc594f43ccaf322762352bb0dc5b80bcb3275b5",
57
- "schema/kit-bundle-manifest.schema.json": "sha256:c15e28ae028dbd1c262427b9c797142e19b1f38fa36b4ecfe187e32c20a174ec",
54
+ "schema/integration-error-details.schema.json": "sha256:0e86425a9c9644ec88f4c55fb2d28454a779a705fb6446d9aa63ce2746a1a55c",
55
+ "schema/integration-execute-request.schema.json": "sha256:1549d7d25525e71696e38f5832aeeddba538c1b2e6f5c5497efc3aa5220a5124",
56
+ "schema/integration-execute-result.schema.json": "sha256:de87c35fb4dfb16f7334cab111f247281fc2300dc27738871ed1f8397ece6753",
57
+ "schema/kit-bundle-manifest.schema.json": "sha256:8700705bcc518a622874ac9ed9650e425adbfc2cfcbe380423ae7f2540fa9072",
58
58
  "schema/kit-lock.schema.json": "sha256:5e24b76660f83990af7a6c037af350c3850ee439ef956f65a0360facbdcdd997",
59
59
  "schema/kvs-routing-entry.schema.json": "sha256:b75f3a5dfa93a428b2764e03222b66b6ef2bed980f8bac8ffac54afdf3526bc5",
60
60
  "schema/promotion-request.schema.json": "sha256:00e96dfd8ddbdba88ea222941d43f3609ff6c815a79e34f6705cab458101c969",
@@ -108,7 +108,7 @@
108
108
  "gen-go/integration_execute_input_rules.go": "sha256:224fb490a5f936b30927e6637e698093e1e42a695cbc1bcf9a39bc70db9f2172",
109
109
  "gen-go/integration_execute_input_rules_test.go": "sha256:c437a8406a2f062a0173b9419e4cb176fc6eb54e569506225d5162323f55b40b",
110
110
  "gen-go/schema_documents.go": "sha256:a01b157658d23e2585e64ff101ed720942856626676b50c83efbd8b044a050d6",
111
- "gen-go/types.go": "sha256:7b764054cd42765ca9532133f0b5f83c0ca4bd7af78ea7bfa7ab4f8ba407891c",
111
+ "gen-go/types.go": "sha256:f59a7a447b907e5a74b69b4d733343ec7a3c0d5178449ffaf97201d577e98b26",
112
112
  "gen-go/version_state.go": "sha256:4ef8abb126414c0025cf2c59bc9c79a74ef4a2ff89e8f316cbf05acc872371c1"
113
113
  },
114
114
  "generatedTypeScript": {
@@ -133,13 +133,13 @@
133
133
  "gen-ts/ai-transform-result.ts": "sha256:208c66b2aeb425c0cbcc18b7191f6e8989443f2b2c1690648e0e06cc1448ce25",
134
134
  "gen-ts/ai-verification-evidence.ts": "sha256:979439d77f8b47631d1eb3b6c3c54ea6fec41089d2a51dc815fd370b1c4917fc",
135
135
  "gen-ts/api-error-response.ts": "sha256:0fd73be282e7a5d72d6c343a4e64d09c232556b16161167f0cffd6278ed7e3d3",
136
- "gen-ts/app-integrations-declaration.ts": "sha256:c2edea82c2c80d15dc233499ef289ea310e59b61b1e4428ea9e4fed85168d2a4",
136
+ "gen-ts/app-integrations-declaration.ts": "sha256:3d0ec11ed1ca5c28edecc5dce3e305bc6cb5d2f64937ef9eee8a10bd5378938c",
137
137
  "gen-ts/artifact-contract.ts": "sha256:4a6dc6d2caaeed4bb2066cc8a333e6e09e5a0d4745e0b1038293d54ab9403aeb",
138
138
  "gen-ts/binding-classification-digest-helper.ts": "sha256:40fc7d85b4c3f3ffc5caf7d7950c62d013c83ee42058c7453b2100d95817f167",
139
139
  "gen-ts/binding-classification-subject.ts": "sha256:c409a3fd7b942b50419632c51d9a79474fb30d6374bdc79c0e25c73618e800c3",
140
140
  "gen-ts/builder-input-proposal.ts": "sha256:b0108c3787873d3cf1a39f00967379418a6766275d3474cfa55f5a9d93ead174",
141
141
  "gen-ts/data-dependency.ts": "sha256:0572c31d7ae9088eccc104eee1a205bf176d1d6dacfaf0f290d666024e284966",
142
- "gen-ts/data-grant-v2.ts": "sha256:d7c78866dfbad6c852bdbf470057a7b926edcadf5563e6ea4da3da9ae96cf699",
142
+ "gen-ts/data-grant-v2.ts": "sha256:b6660cd182b9c73e840bb962e66eafeda28cf003a3596ab8b3572174232da612",
143
143
  "gen-ts/data-grant.ts": "sha256:2358579d8aa1e57b01a6f2b0eacce5531e7f21ef226404c9809925365e0d6155",
144
144
  "gen-ts/data-source.ts": "sha256:77d6bb3b0eaa355789c5d97dd1f548734add6950329dc69549350b8d17c8d582",
145
145
  "gen-ts/deploy-request.ts": "sha256:0214faa8be6680f0bfc117d5ce67c1a59fcc18bd0ea4bc840e03e7d051fa9ab6",
@@ -156,12 +156,12 @@
156
156
  "gen-ts/error-envelope.ts": "sha256:4457309be794f9d4fc0cf62530cc2d442c007bac9ae84aff66df099cc9e40310",
157
157
  "gen-ts/index.ts": "sha256:1fa78bbfd85f29f777f9018d110b70388af35ab2e428c07f75f6701ff3eaba22",
158
158
  "gen-ts/integration-connect-result.ts": "sha256:4b10f169b6e1af364e9b418de21caa9fadbfb77b43cbff77d741cfae0b52180c",
159
- "gen-ts/integration-error-details.ts": "sha256:8243f5871f376d1cab4d90e07f3151d148fb03c704168dbccdfe6c9fe76bce83",
159
+ "gen-ts/integration-error-details.ts": "sha256:46b5ef79491117e125649afa3bd3a16bc72ece78f635fb0daf60816176c515ef",
160
160
  "gen-ts/integration-execute-input-rules.ts": "sha256:a779c872c44bb892dec7b28173d961696d95e5b6e1fa14e2ba67dd78344cdc5b",
161
- "gen-ts/integration-execute-request.ts": "sha256:7fe65e3534544eb6d2a1e7d4813fc1d89c69c148f7ca74ad493a17a7cea6b796",
162
- "gen-ts/integration-execute-result.ts": "sha256:4aaf6a9688f9dfdbc87a408556b62f822518acf5b76548076adb55393c0863b4",
163
- "gen-ts/kit-bundle-manifest.ts": "sha256:9041b32af3df781ca6800bc14c3719e5e54c81dff370633454ac3ad27432eb58",
164
- "gen-ts/kit-lock.ts": "sha256:de4ae82bac7bd234a3230ae671962e80eed6542d695a032a442673659dc6be5e",
161
+ "gen-ts/integration-execute-request.ts": "sha256:353136b98c881efdb559c43617245c0a300cd683b7e876d70e9df96b1fe35cb6",
162
+ "gen-ts/integration-execute-result.ts": "sha256:b7c9270abb0c12888e651aeb1a9927a658faa135ad6197be6dfbd46d942f2a0a",
163
+ "gen-ts/kit-bundle-manifest.ts": "sha256:69e48244848fe8e59189ca84833f1b8f5704ed3e9b0477b3cb025f6ab443f059",
164
+ "gen-ts/kit-lock.ts": "sha256:7ca2286c2c42ff3f177f36057f7b7706f1cc4abc8b0df1de2f8c7a978c98bd78",
165
165
  "gen-ts/kvs-routing-entry.ts": "sha256:d92e85ee9f257d77850d1d3aac9438e2055f38084ff84a48fcedae239d47d4ca",
166
166
  "gen-ts/promotion-request.ts": "sha256:9db88cef67f777a16bac628039ec3f582acb9e8cfff6fb592b30d0a0e55abc68",
167
167
  "gen-ts/promotion-response.ts": "sha256:f1c9cafac96faeb02a443a31786f67714e818f38d90108e7af41958396524d6f",
@@ -9,7 +9,7 @@ export type IntegrationAlias = string;
9
9
  */
10
10
  export type ResourceNames = IntegrationAlias[];
11
11
  /**
12
- * Whose authority an operation runs under. Fixed per operation (slack.message.post: declared per app); never chosen at call time.
12
+ * Whose authority an operation runs under. Fixed per operation, except slack.message.post: declared per app for the access request, and named per call among the app's approved modes (execute request `mode`, 0.55.0).
13
13
  */
14
14
  export type IntegrationIdentityMode = 'app' | 'user';
15
15
  /**
@@ -17,7 +17,7 @@ export type IntegrationIdentityMode = 'app' | 'user';
17
17
  */
18
18
  export type ColumnName = string;
19
19
  /**
20
- * Committed `.harbour/integrations.json`: the app's declared use of tenant-wide integration connections. Keys are logical connection aliases (the governance sourceId); resources are logical names (Slack channel alias, warehouse view name), never IDs, tokens or URLs. The operation set is closed (six operations). Each operation carries a fixed identity mode — slack.channel.history=user, gmail.thread.list=user, gmail.message.read=user, gmail.message.send=user, warehouse.view.read=app — except slack.message.post, whose mode the app DECLARES: `app` posts as the company's Slack app (the one audited bot identity, under the IT-approved per-app display name when the connection declares `presentation`), `user` posts as the signed-in person after their own consent. The mode is never chosen at call time: governance runs the call under the approved grant of the declared mode. A `saas` connection may declare only the Slack and Gmail operations and a `database` connection only warehouse.view.read; `presentation` is a `saas` connection's optional per-app display name and icon for app-mode Slack posts, shown to IT at approval time and applied only from the approved grant. Gmail is user-mode only (reads plus one send); its single resource is the fixed `inbox` alias. This file also owns the shared IntegrationOperation / IntegrationIdentityMode / IntegrationEnvironment / IntegrationConnectionKind / IntegrationPresentation definitions referenced by the other integration schemas.
20
+ * Committed `.harbour/integrations.json`: the app's declared use of tenant-wide integration connections. Keys are logical connection aliases (the governance sourceId); resources are logical names (Slack channel alias, warehouse view name), never IDs, tokens or URLs. The operation set is closed (six operations). Each operation carries a fixed identity mode — slack.channel.history=user, gmail.thread.list=user, gmail.message.read=user, gmail.message.send=user, warehouse.view.read=app — except slack.message.post, whose mode the app DECLARES: `app` posts as the company's Slack app (the one audited bot identity, under the IT-approved per-app display name when the connection declares `presentation`), `user` posts as the signed-in person after their own consent. The declared mode is the one the app requests access for; an app may hold approved grants for both modes after two requests, and each slack.message.post call names which approved mode it runs under (`mode` on the execute request, 0.55.0) — a mode IT did not approve is refused, never swapped for the other. A `saas` connection may declare only the Slack and Gmail operations and a `database` connection only warehouse.view.read; `presentation` is a `saas` connection's optional per-app display name and icon for app-mode Slack posts, shown to IT at approval time and applied only from the approved grant. Gmail is user-mode only (reads plus one send); its single resource is the fixed `inbox` alias. This file also owns the shared IntegrationOperation / IntegrationIdentityMode / IntegrationEnvironment / IntegrationConnectionKind / IntegrationPresentation definitions referenced by the other integration schemas.
21
21
  */
22
22
  export interface AppIntegrationsDeclaration {
23
23
  schema: 'harbour.app-integrations/2.0';
@@ -1,7 +1,7 @@
1
- export declare const ContractRepositoryVersion: "0.54.0.0";
2
- export declare const ContractPublishVersion: "0.54.0";
3
- export declare const ContractPublishTag: "v0.54.0";
4
- export declare const ContractSchemasDigest: "sha256:fc7bd2680afba97f97aa634ea1d75d93acb608268e507b36f225bcdd29d14d62";
1
+ export declare const ContractRepositoryVersion: "0.56.0.0";
2
+ export declare const ContractPublishVersion: "0.56.0";
3
+ export declare const ContractPublishTag: "v0.56.0";
4
+ export declare const ContractSchemasDigest: "sha256:8f17ac7494b6ffc21a69e9cac4bca42743aad4b2a8f0b19aff958ed5dfbda3e0";
5
5
  export declare const ContractAiFixturesDigest: "sha256:edac31c92e146ca1c40130f2fb5649db82e24b8cd1ac758153b90612052fddb6";
6
- export declare const ContractGeneratedGoDigest: "sha256:4a5ac1dfae12d5dab89db517aacf83ffb499b88140f56b428a05dd4a522f9a9e";
7
- export declare const ContractGeneratedTypeScriptDigest: "sha256:b6dd57fd1527d211f7ab9efbf374058251e21dade6bfdb0122053c720a1016cc";
6
+ export declare const ContractGeneratedGoDigest: "sha256:cb58c33f2f08ae7b44afaa5cada2edb69392b8a2309f16de378bc9ba428e9116";
7
+ export declare const ContractGeneratedTypeScriptDigest: "sha256:64a81d10b02820ed26c73046dca7ed9776a6f1277c2a3e954a36c01e1a30d866";
@@ -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.54.0.0";
4
- export const ContractPublishVersion = "0.54.0";
5
- export const ContractPublishTag = "v0.54.0";
6
- export const ContractSchemasDigest = "sha256:fc7bd2680afba97f97aa634ea1d75d93acb608268e507b36f225bcdd29d14d62";
3
+ export const ContractRepositoryVersion = "0.56.0.0";
4
+ export const ContractPublishVersion = "0.56.0";
5
+ export const ContractPublishTag = "v0.56.0";
6
+ export const ContractSchemasDigest = "sha256:8f17ac7494b6ffc21a69e9cac4bca42743aad4b2a8f0b19aff958ed5dfbda3e0";
7
7
  export const ContractAiFixturesDigest = "sha256:edac31c92e146ca1c40130f2fb5649db82e24b8cd1ac758153b90612052fddb6";
8
- export const ContractGeneratedGoDigest = "sha256:4a5ac1dfae12d5dab89db517aacf83ffb499b88140f56b428a05dd4a522f9a9e";
9
- export const ContractGeneratedTypeScriptDigest = "sha256:b6dd57fd1527d211f7ab9efbf374058251e21dade6bfdb0122053c720a1016cc";
8
+ export const ContractGeneratedGoDigest = "sha256:cb58c33f2f08ae7b44afaa5cada2edb69392b8a2309f16de378bc9ba428e9116";
9
+ export const ContractGeneratedTypeScriptDigest = "sha256:64a81d10b02820ed26c73046dca7ed9776a6f1277c2a3e954a36c01e1a30d866";
@@ -23,7 +23,7 @@ export interface DataGrantV2 {
23
23
  */
24
24
  sourceId: string;
25
25
  /**
26
- * Whose authority an operation runs under. Fixed per operation (slack.message.post: declared per app); never chosen at call time.
26
+ * Whose authority an operation runs under. Fixed per operation, except slack.message.post: declared per app for the access request, and named per call among the app's approved modes (execute request `mode`, 0.55.0).
27
27
  */
28
28
  mode: 'app' | 'user';
29
29
  kind: 'saas' | 'database';
@@ -72,7 +72,7 @@ export interface DataGrantV2 {
72
72
  updatedAt: string;
73
73
  /**
74
74
  * @deprecated
75
- * Whose authority an operation runs under. Fixed per operation (slack.message.post: declared per app); never chosen at call time.
75
+ * Whose authority an operation runs under. Fixed per operation, except slack.message.post: declared per app for the access request, and named per call among the app's approved modes (execute request `mode`, 0.55.0).
76
76
  */
77
77
  identityMode?: 'app' | 'user';
78
78
  /**
@@ -3,9 +3,9 @@
3
3
  */
4
4
  export type IntegrationErrorCategory = 'AUTH_REQUIRED' | 'FORBIDDEN' | 'NOT_FOUND' | 'CONFLICT' | 'VALIDATION_FAILED' | 'RATE_LIMITED' | 'UNAVAILABLE';
5
5
  /**
6
- * Stable reason codes. SEND_OUTCOME_UNKNOWN: dispatch happened but the outcome is unknown; never re-post automatically. SEND_KEY_REUSED: the idempotency key was seen with a different payload or subject. GRANT_DENIED: IT denied the request. TOKEN_REFRESH_PENDING: another call holds the refresh lease; retry shortly. REQUEST_PENDING: a different request for this grant is still open. INPUT_INVALID: the operation input failed its bounds. IDEMPOTENCY_KEY_REQUIRED: a send without a UUID idempotencyKey. SEND_IN_PROGRESS: a retry arrived while the first send of that key is still running. CONNECTION_NOT_FOUND / APP_NOT_FOUND / GRANT_NOT_FOUND: the named record does not exist. AUTH_REQUIRED: no verified caller. UNAVAILABLE: the governance dependency (store, provider seam) is unreachable.
6
+ * Stable reason codes. SEND_OUTCOME_UNKNOWN: dispatch happened but the outcome is unknown; never re-post automatically. SEND_KEY_REUSED: the idempotency key was seen with a different payload or subject. GRANT_DENIED: IT denied the request. TOKEN_REFRESH_PENDING: another call holds the refresh lease; retry shortly. REQUEST_PENDING: a different request for this grant is still open. INPUT_INVALID: the operation input failed its bounds. IDEMPOTENCY_KEY_REQUIRED: a send without a UUID idempotencyKey. SEND_IN_PROGRESS: a retry arrived while the first send of that key is still running. CONNECTION_NOT_FOUND / APP_NOT_FOUND / GRANT_NOT_FOUND: the named record does not exist. AUTH_REQUIRED: no verified caller. UNAVAILABLE: the governance dependency (store, provider seam) is unreachable. MODE_NOT_GRANTED (403 FORBIDDEN, 0.55.0): a slack.message.post named a mode (app or user) IT has not approved for this app; the post is never served under the other mode. MODE_REQUIRED (400 VALIDATION_FAILED, 0.55.0): the app holds approved grants for both modes and the post named neither.
7
7
  */
8
- export type IntegrationErrorCode = 'USER_CONNECTION_REQUIRED' | 'USER_RECONNECT_REQUIRED' | 'GRANT_NOT_READY' | 'GRANT_EXPIRED' | 'GRANT_REVOKED' | 'CONNECTION_DISABLED' | 'OPERATION_NOT_DECLARED' | 'RESOURCE_NOT_APPROVED' | 'IDENTITY_MODE_MISMATCH' | 'SEND_OUTCOME_UNKNOWN' | 'SEND_KEY_REUSED' | 'PROVIDER_RATE_LIMITED' | 'PROVIDER_UNAVAILABLE' | 'REQUEST_TOO_LARGE' | 'RESULT_TOO_LARGE' | 'DEADLINE_EXCEEDED' | 'APP_ACCESS_REQUIRED' | 'GRANT_DENIED' | 'TOKEN_REFRESH_PENDING' | 'REQUEST_PENDING' | 'INPUT_INVALID' | 'IDEMPOTENCY_KEY_REQUIRED' | 'SEND_IN_PROGRESS' | 'CONNECTION_NOT_FOUND' | 'APP_NOT_FOUND' | 'GRANT_NOT_FOUND' | 'AUTH_REQUIRED' | 'UNAVAILABLE';
8
+ export type IntegrationErrorCode = 'USER_CONNECTION_REQUIRED' | 'USER_RECONNECT_REQUIRED' | 'GRANT_NOT_READY' | 'GRANT_EXPIRED' | 'GRANT_REVOKED' | 'CONNECTION_DISABLED' | 'OPERATION_NOT_DECLARED' | 'RESOURCE_NOT_APPROVED' | 'IDENTITY_MODE_MISMATCH' | 'SEND_OUTCOME_UNKNOWN' | 'SEND_KEY_REUSED' | 'PROVIDER_RATE_LIMITED' | 'PROVIDER_UNAVAILABLE' | 'REQUEST_TOO_LARGE' | 'RESULT_TOO_LARGE' | 'DEADLINE_EXCEEDED' | 'APP_ACCESS_REQUIRED' | 'GRANT_DENIED' | 'TOKEN_REFRESH_PENDING' | 'REQUEST_PENDING' | 'INPUT_INVALID' | 'IDEMPOTENCY_KEY_REQUIRED' | 'SEND_IN_PROGRESS' | 'CONNECTION_NOT_FOUND' | 'APP_NOT_FOUND' | 'GRANT_NOT_FOUND' | 'AUTH_REQUIRED' | 'UNAVAILABLE' | 'MODE_NOT_GRANTED' | 'MODE_REQUIRED';
9
9
  /**
10
10
  * The `error` member of an integration failure response `{error, requestId}` on the execute/connect routes; the SDK maps it onto HarbourError(category, message, details). `category` is the app-SDK HarbourErrorCategory (not the deployment pipeline's dot-path error-category registry), with the HTTP status fixed per category: 401 AUTH_REQUIRED, 403 FORBIDDEN (denied/revoked/expired), 404 NOT_FOUND (unknown app/connection/resource), 409 CONFLICT (pending provisioning/consent/concurrent request), 400 or 413 VALIDATION_FAILED (invalid input / size overflow), 429 RATE_LIMITED (+ Retry-After header), 503 UNAVAILABLE. `details.code` is the closed stable reason set; `message` is human-facing and never carries provider error bodies, tokens or SQL.
11
11
  */
@@ -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 NOT a caller field: it is fixed by the operation (declared per app for slack.message.post) and enforced from the approved grant; any identity/userId/scope field is rejected by additionalProperties. A slack.message.post runs as the app (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), whichever mode the app declared and IT approved — the same input either way. Per-operation input bounds: slack.channel.history {limit? 1..15, default 15}; slack.message.post {text 1..4000 chars} and a required idempotencyKey retained across retries of one explicit Send; gmail.thread.list {limit? 1..15, default 15}; gmail.message.read {messageId}; gmail.message.send {to 1..50 addresses (to+cc+bcc <= 50 in total), cc?, bcc?, subject 1..256 chars, text 1..4000 chars, replyToMessageId?} and a required idempotencyKey like slack.message.post (plain text only, no attachments); warehouse.view.read {columns 1..32, limit? 1..1000, default 100}. 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, 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 slack.message.post, and always enforced from an approved grant; any identity/userId/scope field is rejected by additionalProperties. A slack.message.post runs as the app (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 post does. Per-operation input bounds: slack.channel.history {limit? 1..15, default 15}; slack.message.post {text 1..4000 chars} and a required idempotencyKey retained across retries of one explicit Send; gmail.thread.list {limit? 1..15, default 15}; gmail.message.read {messageId}; gmail.message.send {to 1..50 addresses (to+cc+bcc <= 50 in total), cc?, bcc?, subject 1..256 chars, text 1..4000 chars, replyToMessageId?} and a required idempotencyKey like slack.message.post (plain text only, no attachments); warehouse.view.read {columns 1..32, limit? 1..1000, default 100}. 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, 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. gmail.message.send (0.50.0) sends one plain-text email as the signed-in person: a send like slack.message.post (explicit Send, idempotencyKey, write receipt) under user identity. 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).
10
+ * Closed operation set. Extending it is a contracts change. gmail.message.send (0.50.0) sends one plain-text email as the signed-in person: a send like slack.message.post (explicit Send, idempotencyKey, write receipt) under user identity. 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.
11
11
  */
12
12
  operation: 'slack.channel.history' | 'slack.message.post' | 'gmail.thread.list' | 'gmail.message.read' | 'gmail.message.send' | 'warehouse.view.read';
13
13
  /**
@@ -48,6 +48,10 @@ export type IntegrationExecuteRequest = {
48
48
  * UUID of one explicit Send action; required for slack.message.post and gmail.message.send only. The same key with the same payload and subject returns the recorded outcome for seven days.
49
49
  */
50
50
  idempotencyKey?: string;
51
+ /**
52
+ * slack.message.post only (0.55.0): which of the app's IT-approved modes this post runs under — app (the company's Slack app, under the grant's per-app name and the footer) or user (the signed-in person, after their consent; no name, no footer). Required when the app holds approved grants for both modes (else MODE_REQUIRED); may be omitted when it holds one, which is then used. A named mode without an approved grant is refused with MODE_NOT_GRANTED; governance never falls back to the other mode. Every other operation's mode is fixed, so the field is refused there.
53
+ */
54
+ mode?: 'app' | 'user';
51
55
  };
52
56
  /**
53
57
  * One bare email address (no display name).
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The `data` member of a successful execute response `{data, requestId}`; the SDK unwraps `data`. Exactly one of the six operation results: slack.channel.history -> {messages, hasMore} (plain text/IDs/time only, never the provider payload); slack.message.post -> {messageId}; gmail.thread.list -> {threads, hasMore} (newest inbox threads; each `messageId` is the thread's newest message, what gmail.message.read takes); gmail.message.read -> {id, threadId, subject, from, to, date, text, truncated} (headers plus the plain text with html stripped, cut at 16 KiB with `truncated` set); gmail.message.send -> {messageId, threadId} (the Gmail ids of the sent message and its thread); warehouse.view.read -> {rows} (decimal/64-bit values as strings, dates as ISO strings). An unknown send outcome is never a result; it is an error with details.code SEND_OUTCOME_UNKNOWN. Results over 1 MiB are rejected, not truncated.
2
+ * The `data` member of a successful execute response `{data, requestId}`; the SDK unwraps `data`. Exactly one of the six operation results: slack.channel.history -> {messages, hasMore} (plain text/IDs/time, plus the display name and emoji icon a per-app-named app post carries — authorName/authorIcon, 0.55.0 — never the provider payload); slack.message.post -> {messageId}; gmail.thread.list -> {threads, hasMore} (newest inbox threads; each `messageId` is the thread's newest message, what gmail.message.read takes); gmail.message.read -> {id, threadId, subject, from, to, date, text, truncated} (headers plus the plain text with html stripped, cut at 16 KiB with `truncated` set); gmail.message.send -> {messageId, threadId} (the Gmail ids of the sent message and its thread); warehouse.view.read -> {rows} (decimal/64-bit values as strings, dates as ISO strings). An unknown send outcome is never a result; it is an error with details.code SEND_OUTCOME_UNKNOWN. Results over 1 MiB are rejected, not truncated.
3
3
  */
4
4
  export type IntegrationExecuteResult = {
5
5
  /**
@@ -32,6 +32,14 @@ export interface ChannelMessage {
32
32
  text: string;
33
33
  authorId: string;
34
34
  createdAt: string;
35
+ /**
36
+ * The display name a per-app-named app post carries (0.55.0; Slack's `username` on a customised bot message), so an app can recognise its own posts in a channel; absent on a person's message and on a post under the bot's own name.
37
+ */
38
+ authorName?: string;
39
+ /**
40
+ * The emoji shortcode such a post carries as its icon (0.55.0), when it has one; never an image URL.
41
+ */
42
+ authorIcon?: string;
35
43
  }
36
44
  export interface GmailThread {
37
45
  id: string;
@@ -2,6 +2,7 @@
2
2
  * Digest-pinned image reference <repository>@sha256:<hex>; tags are never accepted.
3
3
  */
4
4
  export type ImageDigestReference = string;
5
+ export type HexSha256 = string;
5
6
  /**
6
7
  * Identity of one published development-kit bundle (shipped with the CLI). It pins the app SDK tarball, the local runtime images and the agent-brief fingerprint that a kit-built app was developed against, so the hosted pipeline can prove compatibility before replacing the vendored SDK (see kit-lock.schema.json).
7
8
  */
@@ -30,6 +31,12 @@ export interface KitBundleManifest {
30
31
  appGateway: ImageDigestReference;
31
32
  sessionFixture: ImageDigestReference;
32
33
  };
34
+ /**
35
+ * Native local App Gateway executables. The first release supports Apple Silicon; hosted workloads continue to use images.
36
+ */
37
+ nativeRuntime?: {
38
+ darwinArm64?: NativeExecutable;
39
+ };
33
40
  brief: {
34
41
  /**
35
42
  * SHA-256 of the agent-brief tar, or the literal unknown when the bundle carried no brief.
@@ -38,3 +45,7 @@ export interface KitBundleManifest {
38
45
  };
39
46
  declarationSchema: 'harbour.app-integrations/2.0';
40
47
  }
48
+ export interface NativeExecutable {
49
+ url: string;
50
+ sha256: HexSha256;
51
+ }
@@ -43,6 +43,12 @@ interface KitBundleManifest {
43
43
  */
44
44
  sessionFixture: string;
45
45
  };
46
+ /**
47
+ * Native local App Gateway executables. The first release supports Apple Silicon; hosted workloads continue to use images.
48
+ */
49
+ nativeRuntime?: {
50
+ darwinArm64?: NativeExecutable;
51
+ };
46
52
  brief: {
47
53
  /**
48
54
  * SHA-256 of the agent-brief tar, or the literal unknown when the bundle carried no brief.
@@ -51,4 +57,8 @@ interface KitBundleManifest {
51
57
  };
52
58
  declarationSchema: 'harbour.app-integrations/2.0';
53
59
  }
60
+ interface NativeExecutable {
61
+ url: string;
62
+ sha256: string;
63
+ }
54
64
  export {};
@@ -12,7 +12,7 @@ export type IntegrationAlias = string;
12
12
  */
13
13
  export type ResourceNames = IntegrationAlias[];
14
14
  /**
15
- * Whose authority an operation runs under. Fixed per operation (slack.message.post: declared per app); never chosen at call time.
15
+ * Whose authority an operation runs under. Fixed per operation, except slack.message.post: declared per app for the access request, and named per call among the app's approved modes (execute request `mode`, 0.55.0).
16
16
  */
17
17
  export type IntegrationIdentityMode = 'app' | 'user';
18
18
  /**
@@ -21,7 +21,7 @@ export type IntegrationIdentityMode = 'app' | 'user';
21
21
  export type ColumnName = string;
22
22
 
23
23
  /**
24
- * Committed `.harbour/integrations.json`: the app's declared use of tenant-wide integration connections. Keys are logical connection aliases (the governance sourceId); resources are logical names (Slack channel alias, warehouse view name), never IDs, tokens or URLs. The operation set is closed (six operations). Each operation carries a fixed identity mode — slack.channel.history=user, gmail.thread.list=user, gmail.message.read=user, gmail.message.send=user, warehouse.view.read=app — except slack.message.post, whose mode the app DECLARES: `app` posts as the company's Slack app (the one audited bot identity, under the IT-approved per-app display name when the connection declares `presentation`), `user` posts as the signed-in person after their own consent. The mode is never chosen at call time: governance runs the call under the approved grant of the declared mode. A `saas` connection may declare only the Slack and Gmail operations and a `database` connection only warehouse.view.read; `presentation` is a `saas` connection's optional per-app display name and icon for app-mode Slack posts, shown to IT at approval time and applied only from the approved grant. Gmail is user-mode only (reads plus one send); its single resource is the fixed `inbox` alias. This file also owns the shared IntegrationOperation / IntegrationIdentityMode / IntegrationEnvironment / IntegrationConnectionKind / IntegrationPresentation definitions referenced by the other integration schemas.
24
+ * Committed `.harbour/integrations.json`: the app's declared use of tenant-wide integration connections. Keys are logical connection aliases (the governance sourceId); resources are logical names (Slack channel alias, warehouse view name), never IDs, tokens or URLs. The operation set is closed (six operations). Each operation carries a fixed identity mode — slack.channel.history=user, gmail.thread.list=user, gmail.message.read=user, gmail.message.send=user, warehouse.view.read=app — except slack.message.post, whose mode the app DECLARES: `app` posts as the company's Slack app (the one audited bot identity, under the IT-approved per-app display name when the connection declares `presentation`), `user` posts as the signed-in person after their own consent. The declared mode is the one the app requests access for; an app may hold approved grants for both modes after two requests, and each slack.message.post call names which approved mode it runs under (`mode` on the execute request, 0.55.0) — a mode IT did not approve is refused, never swapped for the other. A `saas` connection may declare only the Slack and Gmail operations and a `database` connection only warehouse.view.read; `presentation` is a `saas` connection's optional per-app display name and icon for app-mode Slack posts, shown to IT at approval time and applied only from the approved grant. Gmail is user-mode only (reads plus one send); its single resource is the fixed `inbox` alias. This file also owns the shared IntegrationOperation / IntegrationIdentityMode / IntegrationEnvironment / IntegrationConnectionKind / IntegrationPresentation definitions referenced by the other integration schemas.
25
25
  */
26
26
  export interface AppIntegrationsDeclaration {
27
27
  schema: 'harbour.app-integrations/2.0';
@@ -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.54.0.0" as const;
5
- export const ContractPublishVersion = "0.54.0" as const;
6
- export const ContractPublishTag = "v0.54.0" as const;
7
- export const ContractSchemasDigest = "sha256:fc7bd2680afba97f97aa634ea1d75d93acb608268e507b36f225bcdd29d14d62" as const;
4
+ export const ContractRepositoryVersion = "0.56.0.0" as const;
5
+ export const ContractPublishVersion = "0.56.0" as const;
6
+ export const ContractPublishTag = "v0.56.0" as const;
7
+ export const ContractSchemasDigest = "sha256:8f17ac7494b6ffc21a69e9cac4bca42743aad4b2a8f0b19aff958ed5dfbda3e0" as const;
8
8
  export const ContractAiFixturesDigest = "sha256:edac31c92e146ca1c40130f2fb5649db82e24b8cd1ac758153b90612052fddb6" as const;
9
- export const ContractGeneratedGoDigest = "sha256:4a5ac1dfae12d5dab89db517aacf83ffb499b88140f56b428a05dd4a522f9a9e" as const;
10
- export const ContractGeneratedTypeScriptDigest = "sha256:b6dd57fd1527d211f7ab9efbf374058251e21dade6bfdb0122053c720a1016cc" as const;
9
+ export const ContractGeneratedGoDigest = "sha256:cb58c33f2f08ae7b44afaa5cada2edb69392b8a2309f16de378bc9ba428e9116" as const;
10
+ export const ContractGeneratedTypeScriptDigest = "sha256:64a81d10b02820ed26c73046dca7ed9776a6f1277c2a3e954a36c01e1a30d866" as const;
@@ -27,7 +27,7 @@ export interface DataGrantV2 {
27
27
  */
28
28
  sourceId: string;
29
29
  /**
30
- * Whose authority an operation runs under. Fixed per operation (slack.message.post: declared per app); never chosen at call time.
30
+ * Whose authority an operation runs under. Fixed per operation, except slack.message.post: declared per app for the access request, and named per call among the app's approved modes (execute request `mode`, 0.55.0).
31
31
  */
32
32
  mode: 'app' | 'user';
33
33
  kind: 'saas' | 'database';
@@ -83,7 +83,7 @@ export interface DataGrantV2 {
83
83
  updatedAt: string;
84
84
  /**
85
85
  * @deprecated
86
- * Whose authority an operation runs under. Fixed per operation (slack.message.post: declared per app); never chosen at call time.
86
+ * Whose authority an operation runs under. Fixed per operation, except slack.message.post: declared per app for the access request, and named per call among the app's approved modes (execute request `mode`, 0.55.0).
87
87
  */
88
88
  identityMode?: 'app' | 'user';
89
89
  /**
@@ -7,7 +7,7 @@
7
7
  export type IntegrationErrorCategory =
8
8
  'AUTH_REQUIRED' | 'FORBIDDEN' | 'NOT_FOUND' | 'CONFLICT' | 'VALIDATION_FAILED' | 'RATE_LIMITED' | 'UNAVAILABLE';
9
9
  /**
10
- * Stable reason codes. SEND_OUTCOME_UNKNOWN: dispatch happened but the outcome is unknown; never re-post automatically. SEND_KEY_REUSED: the idempotency key was seen with a different payload or subject. GRANT_DENIED: IT denied the request. TOKEN_REFRESH_PENDING: another call holds the refresh lease; retry shortly. REQUEST_PENDING: a different request for this grant is still open. INPUT_INVALID: the operation input failed its bounds. IDEMPOTENCY_KEY_REQUIRED: a send without a UUID idempotencyKey. SEND_IN_PROGRESS: a retry arrived while the first send of that key is still running. CONNECTION_NOT_FOUND / APP_NOT_FOUND / GRANT_NOT_FOUND: the named record does not exist. AUTH_REQUIRED: no verified caller. UNAVAILABLE: the governance dependency (store, provider seam) is unreachable.
10
+ * Stable reason codes. SEND_OUTCOME_UNKNOWN: dispatch happened but the outcome is unknown; never re-post automatically. SEND_KEY_REUSED: the idempotency key was seen with a different payload or subject. GRANT_DENIED: IT denied the request. TOKEN_REFRESH_PENDING: another call holds the refresh lease; retry shortly. REQUEST_PENDING: a different request for this grant is still open. INPUT_INVALID: the operation input failed its bounds. IDEMPOTENCY_KEY_REQUIRED: a send without a UUID idempotencyKey. SEND_IN_PROGRESS: a retry arrived while the first send of that key is still running. CONNECTION_NOT_FOUND / APP_NOT_FOUND / GRANT_NOT_FOUND: the named record does not exist. AUTH_REQUIRED: no verified caller. UNAVAILABLE: the governance dependency (store, provider seam) is unreachable. MODE_NOT_GRANTED (403 FORBIDDEN, 0.55.0): a slack.message.post named a mode (app or user) IT has not approved for this app; the post is never served under the other mode. MODE_REQUIRED (400 VALIDATION_FAILED, 0.55.0): the app holds approved grants for both modes and the post named neither.
11
11
  */
12
12
  export type IntegrationErrorCode =
13
13
  | 'USER_CONNECTION_REQUIRED'
@@ -37,7 +37,9 @@ export type IntegrationErrorCode =
37
37
  | 'APP_NOT_FOUND'
38
38
  | 'GRANT_NOT_FOUND'
39
39
  | 'AUTH_REQUIRED'
40
- | 'UNAVAILABLE';
40
+ | 'UNAVAILABLE'
41
+ | 'MODE_NOT_GRANTED'
42
+ | 'MODE_REQUIRED';
41
43
 
42
44
  /**
43
45
  * The `error` member of an integration failure response `{error, requestId}` on the execute/connect routes; the SDK maps it onto HarbourError(category, message, details). `category` is the app-SDK HarbourErrorCategory (not the deployment pipeline's dot-path error-category registry), with the HTTP status fixed per category: 401 AUTH_REQUIRED, 403 FORBIDDEN (denied/revoked/expired), 404 NOT_FOUND (unknown app/connection/resource), 409 CONFLICT (pending provisioning/consent/concurrent request), 400 or 413 VALIDATION_FAILED (invalid input / size overflow), 429 RATE_LIMITED (+ Retry-After header), 503 UNAVAILABLE. `details.code` is the closed stable reason set; `message` is human-facing and never carries provider error bodies, tokens or SQL.
@@ -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 NOT a caller field: it is fixed by the operation (declared per app for slack.message.post) and enforced from the approved grant; any identity/userId/scope field is rejected by additionalProperties. A slack.message.post runs as the app (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), whichever mode the app declared and IT approved — the same input either way. Per-operation input bounds: slack.channel.history {limit? 1..15, default 15}; slack.message.post {text 1..4000 chars} and a required idempotencyKey retained across retries of one explicit Send; gmail.thread.list {limit? 1..15, default 15}; gmail.message.read {messageId}; gmail.message.send {to 1..50 addresses (to+cc+bcc <= 50 in total), cc?, bcc?, subject 1..256 chars, text 1..4000 chars, replyToMessageId?} and a required idempotencyKey like slack.message.post (plain text only, no attachments); warehouse.view.read {columns 1..32, limit? 1..1000, default 100}. 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, 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 slack.message.post, and always enforced from an approved grant; any identity/userId/scope field is rejected by additionalProperties. A slack.message.post runs as the app (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 post does. Per-operation input bounds: slack.channel.history {limit? 1..15, default 15}; slack.message.post {text 1..4000 chars} and a required idempotencyKey retained across retries of one explicit Send; gmail.thread.list {limit? 1..15, default 15}; gmail.message.read {messageId}; gmail.message.send {to 1..50 addresses (to+cc+bcc <= 50 in total), cc?, bcc?, subject 1..256 chars, text 1..4000 chars, replyToMessageId?} and a required idempotencyKey like slack.message.post (plain text only, no attachments); warehouse.view.read {columns 1..32, limit? 1..1000, default 100}. 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, 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. gmail.message.send (0.50.0) sends one plain-text email as the signed-in person: a send like slack.message.post (explicit Send, idempotencyKey, write receipt) under user identity. 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).
13
+ * Closed operation set. Extending it is a contracts change. gmail.message.send (0.50.0) sends one plain-text email as the signed-in person: a send like slack.message.post (explicit Send, idempotencyKey, write receipt) under user identity. 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.
14
14
  */
15
15
  operation:
16
16
  | 'slack.channel.history'
@@ -57,6 +57,10 @@ export type IntegrationExecuteRequest = {
57
57
  * UUID of one explicit Send action; required for slack.message.post and gmail.message.send only. The same key with the same payload and subject returns the recorded outcome for seven days.
58
58
  */
59
59
  idempotencyKey?: string;
60
+ /**
61
+ * slack.message.post only (0.55.0): which of the app's IT-approved modes this post runs under — app (the company's Slack app, under the grant's per-app name and the footer) or user (the signed-in person, after their consent; no name, no footer). Required when the app holds approved grants for both modes (else MODE_REQUIRED); may be omitted when it holds one, which is then used. A named mode without an approved grant is refused with MODE_NOT_GRANTED; governance never falls back to the other mode. Every other operation's mode is fixed, so the field is refused there.
62
+ */
63
+ mode?: 'app' | 'user';
60
64
  };
61
65
  /**
62
66
  * One bare email address (no display name).
@@ -2,7 +2,7 @@
2
2
  // Code generated by tools/gen/gen.mjs from schema/*.json. DO NOT EDIT.
3
3
 
4
4
  /**
5
- * The `data` member of a successful execute response `{data, requestId}`; the SDK unwraps `data`. Exactly one of the six operation results: slack.channel.history -> {messages, hasMore} (plain text/IDs/time only, never the provider payload); slack.message.post -> {messageId}; gmail.thread.list -> {threads, hasMore} (newest inbox threads; each `messageId` is the thread's newest message, what gmail.message.read takes); gmail.message.read -> {id, threadId, subject, from, to, date, text, truncated} (headers plus the plain text with html stripped, cut at 16 KiB with `truncated` set); gmail.message.send -> {messageId, threadId} (the Gmail ids of the sent message and its thread); warehouse.view.read -> {rows} (decimal/64-bit values as strings, dates as ISO strings). An unknown send outcome is never a result; it is an error with details.code SEND_OUTCOME_UNKNOWN. Results over 1 MiB are rejected, not truncated.
5
+ * The `data` member of a successful execute response `{data, requestId}`; the SDK unwraps `data`. Exactly one of the six operation results: slack.channel.history -> {messages, hasMore} (plain text/IDs/time, plus the display name and emoji icon a per-app-named app post carries — authorName/authorIcon, 0.55.0 — never the provider payload); slack.message.post -> {messageId}; gmail.thread.list -> {threads, hasMore} (newest inbox threads; each `messageId` is the thread's newest message, what gmail.message.read takes); gmail.message.read -> {id, threadId, subject, from, to, date, text, truncated} (headers plus the plain text with html stripped, cut at 16 KiB with `truncated` set); gmail.message.send -> {messageId, threadId} (the Gmail ids of the sent message and its thread); warehouse.view.read -> {rows} (decimal/64-bit values as strings, dates as ISO strings). An unknown send outcome is never a result; it is an error with details.code SEND_OUTCOME_UNKNOWN. Results over 1 MiB are rejected, not truncated.
6
6
  */
7
7
  export type IntegrationExecuteResult = {
8
8
  /**
@@ -36,6 +36,14 @@ export interface ChannelMessage {
36
36
  text: string;
37
37
  authorId: string;
38
38
  createdAt: string;
39
+ /**
40
+ * The display name a per-app-named app post carries (0.55.0; Slack's `username` on a customised bot message), so an app can recognise its own posts in a channel; absent on a person's message and on a post under the bot's own name.
41
+ */
42
+ authorName?: string;
43
+ /**
44
+ * The emoji shortcode such a post carries as its icon (0.55.0), when it has one; never an image URL.
45
+ */
46
+ authorIcon?: string;
39
47
  }
40
48
  export interface GmailThread {
41
49
  id: string;
@@ -5,6 +5,7 @@
5
5
  * Digest-pinned image reference <repository>@sha256:<hex>; tags are never accepted.
6
6
  */
7
7
  export type ImageDigestReference = string;
8
+ export type HexSha256 = string;
8
9
 
9
10
  /**
10
11
  * Identity of one published development-kit bundle (shipped with the CLI). It pins the app SDK tarball, the local runtime images and the agent-brief fingerprint that a kit-built app was developed against, so the hosted pipeline can prove compatibility before replacing the vendored SDK (see kit-lock.schema.json).
@@ -34,6 +35,12 @@ export interface KitBundleManifest {
34
35
  appGateway: ImageDigestReference;
35
36
  sessionFixture: ImageDigestReference;
36
37
  };
38
+ /**
39
+ * Native local App Gateway executables. The first release supports Apple Silicon; hosted workloads continue to use images.
40
+ */
41
+ nativeRuntime?: {
42
+ darwinArm64?: NativeExecutable;
43
+ };
37
44
  brief: {
38
45
  /**
39
46
  * SHA-256 of the agent-brief tar, or the literal unknown when the bundle carried no brief.
@@ -42,3 +49,7 @@ export interface KitBundleManifest {
42
49
  };
43
50
  declarationSchema: 'harbour.app-integrations/2.0';
44
51
  }
52
+ export interface NativeExecutable {
53
+ url: string;
54
+ sha256: HexSha256;
55
+ }
@@ -46,6 +46,12 @@ interface KitBundleManifest {
46
46
  */
47
47
  sessionFixture: string;
48
48
  };
49
+ /**
50
+ * Native local App Gateway executables. The first release supports Apple Silicon; hosted workloads continue to use images.
51
+ */
52
+ nativeRuntime?: {
53
+ darwinArm64?: NativeExecutable;
54
+ };
49
55
  brief: {
50
56
  /**
51
57
  * SHA-256 of the agent-brief tar, or the literal unknown when the bundle carried no brief.
@@ -54,3 +60,7 @@ interface KitBundleManifest {
54
60
  };
55
61
  declarationSchema: 'harbour.app-integrations/2.0';
56
62
  }
63
+ interface NativeExecutable {
64
+ url: string;
65
+ sha256: string;
66
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fourier-labs/harbour-contracts",
3
- "version": "0.54.0",
3
+ "version": "0.56.0",
4
4
  "description": "Single source of truth for every data shape that crosses a boundary between Harbour components.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -2,7 +2,7 @@
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
3
  "$id": "https://schemas.harbour.dev/app-integrations-declaration.schema.json",
4
4
  "title": "AppIntegrationsDeclaration",
5
- "description": "Committed `.harbour/integrations.json`: the app's declared use of tenant-wide integration connections. Keys are logical connection aliases (the governance sourceId); resources are logical names (Slack channel alias, warehouse view name), never IDs, tokens or URLs. The operation set is closed (six operations). Each operation carries a fixed identity mode — slack.channel.history=user, gmail.thread.list=user, gmail.message.read=user, gmail.message.send=user, warehouse.view.read=app — except slack.message.post, whose mode the app DECLARES: `app` posts as the company's Slack app (the one audited bot identity, under the IT-approved per-app display name when the connection declares `presentation`), `user` posts as the signed-in person after their own consent. The mode is never chosen at call time: governance runs the call under the approved grant of the declared mode. A `saas` connection may declare only the Slack and Gmail operations and a `database` connection only warehouse.view.read; `presentation` is a `saas` connection's optional per-app display name and icon for app-mode Slack posts, shown to IT at approval time and applied only from the approved grant. Gmail is user-mode only (reads plus one send); its single resource is the fixed `inbox` alias. This file also owns the shared IntegrationOperation / IntegrationIdentityMode / IntegrationEnvironment / IntegrationConnectionKind / IntegrationPresentation definitions referenced by the other integration schemas.",
5
+ "description": "Committed `.harbour/integrations.json`: the app's declared use of tenant-wide integration connections. Keys are logical connection aliases (the governance sourceId); resources are logical names (Slack channel alias, warehouse view name), never IDs, tokens or URLs. The operation set is closed (six operations). Each operation carries a fixed identity mode — slack.channel.history=user, gmail.thread.list=user, gmail.message.read=user, gmail.message.send=user, warehouse.view.read=app — except slack.message.post, whose mode the app DECLARES: `app` posts as the company's Slack app (the one audited bot identity, under the IT-approved per-app display name when the connection declares `presentation`), `user` posts as the signed-in person after their own consent. The declared mode is the one the app requests access for; an app may hold approved grants for both modes after two requests, and each slack.message.post call names which approved mode it runs under (`mode` on the execute request, 0.55.0) — a mode IT did not approve is refused, never swapped for the other. A `saas` connection may declare only the Slack and Gmail operations and a `database` connection only warehouse.view.read; `presentation` is a `saas` connection's optional per-app display name and icon for app-mode Slack posts, shown to IT at approval time and applied only from the approved grant. Gmail is user-mode only (reads plus one send); its single resource is the fixed `inbox` alias. This file also owns the shared IntegrationOperation / IntegrationIdentityMode / IntegrationEnvironment / IntegrationConnectionKind / IntegrationPresentation definitions referenced by the other integration schemas.",
6
6
  "type": "object",
7
7
  "additionalProperties": false,
8
8
  "required": ["schema", "connections"],
@@ -26,12 +26,12 @@
26
26
  "IntegrationOperation": {
27
27
  "type": "string",
28
28
  "enum": ["slack.channel.history", "slack.message.post", "gmail.thread.list", "gmail.message.read", "gmail.message.send", "warehouse.view.read"],
29
- "description": "Closed operation set. Extending it is a contracts change. gmail.message.send (0.50.0) sends one plain-text email as the signed-in person: a send like slack.message.post (explicit Send, idempotencyKey, write receipt) under user identity. 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)."
29
+ "description": "Closed operation set. Extending it is a contracts change. gmail.message.send (0.50.0) sends one plain-text email as the signed-in person: a send like slack.message.post (explicit Send, idempotencyKey, write receipt) under user identity. 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."
30
30
  },
31
31
  "IntegrationIdentityMode": {
32
32
  "type": "string",
33
33
  "enum": ["app", "user"],
34
- "description": "Whose authority an operation runs under. Fixed per operation (slack.message.post: declared per app); never chosen at call time."
34
+ "description": "Whose authority an operation runs under. Fixed per operation, except slack.message.post: declared per app for the access request, and named per call among the app's approved modes (execute request `mode`, 0.55.0)."
35
35
  },
36
36
  "IntegrationEnvironment": {
37
37
  "type": "string",
@@ -60,9 +60,11 @@
60
60
  "APP_NOT_FOUND",
61
61
  "GRANT_NOT_FOUND",
62
62
  "AUTH_REQUIRED",
63
- "UNAVAILABLE"
63
+ "UNAVAILABLE",
64
+ "MODE_NOT_GRANTED",
65
+ "MODE_REQUIRED"
64
66
  ],
65
- "description": "Stable reason codes. SEND_OUTCOME_UNKNOWN: dispatch happened but the outcome is unknown; never re-post automatically. SEND_KEY_REUSED: the idempotency key was seen with a different payload or subject. GRANT_DENIED: IT denied the request. TOKEN_REFRESH_PENDING: another call holds the refresh lease; retry shortly. REQUEST_PENDING: a different request for this grant is still open. INPUT_INVALID: the operation input failed its bounds. IDEMPOTENCY_KEY_REQUIRED: a send without a UUID idempotencyKey. SEND_IN_PROGRESS: a retry arrived while the first send of that key is still running. CONNECTION_NOT_FOUND / APP_NOT_FOUND / GRANT_NOT_FOUND: the named record does not exist. AUTH_REQUIRED: no verified caller. UNAVAILABLE: the governance dependency (store, provider seam) is unreachable."
67
+ "description": "Stable reason codes. SEND_OUTCOME_UNKNOWN: dispatch happened but the outcome is unknown; never re-post automatically. SEND_KEY_REUSED: the idempotency key was seen with a different payload or subject. GRANT_DENIED: IT denied the request. TOKEN_REFRESH_PENDING: another call holds the refresh lease; retry shortly. REQUEST_PENDING: a different request for this grant is still open. INPUT_INVALID: the operation input failed its bounds. IDEMPOTENCY_KEY_REQUIRED: a send without a UUID idempotencyKey. SEND_IN_PROGRESS: a retry arrived while the first send of that key is still running. CONNECTION_NOT_FOUND / APP_NOT_FOUND / GRANT_NOT_FOUND: the named record does not exist. AUTH_REQUIRED: no verified caller. UNAVAILABLE: the governance dependency (store, provider seam) is unreachable. MODE_NOT_GRANTED (403 FORBIDDEN, 0.55.0): a slack.message.post named a mode (app or user) IT has not approved for this app; the post is never served under the other mode. MODE_REQUIRED (400 VALIDATION_FAILED, 0.55.0): the app holds approved grants for both modes and the post named neither."
66
68
  }
67
69
  },
68
70
  "examples": [
@@ -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 NOT a caller field: it is fixed by the operation (declared per app for slack.message.post) and enforced from the approved grant; any identity/userId/scope field is rejected by additionalProperties. A slack.message.post runs as the app (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), whichever mode the app declared and IT approved — the same input either way. Per-operation input bounds: slack.channel.history {limit? 1..15, default 15}; slack.message.post {text 1..4000 chars} and a required idempotencyKey retained across retries of one explicit Send; gmail.thread.list {limit? 1..15, default 15}; gmail.message.read {messageId}; gmail.message.send {to 1..50 addresses (to+cc+bcc <= 50 in total), cc?, bcc?, subject 1..256 chars, text 1..4000 chars, replyToMessageId?} and a required idempotencyKey like slack.message.post (plain text only, no attachments); warehouse.view.read {columns 1..32, limit? 1..1000, default 100}. 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, 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 slack.message.post, and always enforced from an approved grant; any identity/userId/scope field is rejected by additionalProperties. A slack.message.post runs as the app (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 post does. Per-operation input bounds: slack.channel.history {limit? 1..15, default 15}; slack.message.post {text 1..4000 chars} and a required idempotencyKey retained across retries of one explicit Send; gmail.thread.list {limit? 1..15, default 15}; gmail.message.read {messageId}; gmail.message.send {to 1..50 addresses (to+cc+bcc <= 50 in total), cc?, bcc?, subject 1..256 chars, text 1..4000 chars, replyToMessageId?} and a required idempotencyKey like slack.message.post (plain text only, no attachments); warehouse.view.read {columns 1..32, limit? 1..1000, default 100}. 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, total deadline 10 s.",
6
6
  "type": "object",
7
7
  "additionalProperties": false,
8
8
  "required": ["connection", "operation", "resource", "input"],
@@ -34,6 +34,10 @@
34
34
  "type": "string",
35
35
  "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
36
36
  "description": "UUID of one explicit Send action; required for slack.message.post and gmail.message.send only. The same key with the same payload and subject returns the recorded outcome for seven days."
37
+ },
38
+ "mode": {
39
+ "$ref": "app-integrations-declaration.schema.json#/definitions/IntegrationIdentityMode",
40
+ "description": "slack.message.post only (0.55.0): which of the app's IT-approved modes this post runs under — app (the company's Slack app, under the grant's per-app name and the footer) or user (the signed-in person, after their consent; no name, no footer). Required when the app holds approved grants for both modes (else MODE_REQUIRED); may be omitted when it holds one, which is then used. A named mode without an approved grant is refused with MODE_NOT_GRANTED; governance never falls back to the other mode. Every other operation's mode is fixed, so the field is refused there."
37
41
  }
38
42
  },
39
43
  "allOf": [
@@ -42,6 +46,7 @@
42
46
  "then": {
43
47
  "not": { "required": ["idempotencyKey"] },
44
48
  "properties": {
49
+ "mode": false,
45
50
  "input": { "propertyNames": { "enum": ["limit"] }, "properties": { "limit": { "maximum": 15, "default": 15 } } }
46
51
  }
47
52
  }
@@ -60,6 +65,7 @@
60
65
  "then": {
61
66
  "not": { "required": ["idempotencyKey"] },
62
67
  "properties": {
68
+ "mode": false,
63
69
  "input": { "propertyNames": { "enum": ["columns", "limit"] }, "required": ["columns"], "properties": { "limit": { "maximum": 1000, "default": 100 } } }
64
70
  }
65
71
  }
@@ -69,6 +75,7 @@
69
75
  "then": {
70
76
  "not": { "required": ["idempotencyKey"] },
71
77
  "properties": {
78
+ "mode": false,
72
79
  "input": { "propertyNames": { "enum": ["limit"] }, "properties": { "limit": { "maximum": 15, "default": 15 } } }
73
80
  }
74
81
  }
@@ -78,6 +85,7 @@
78
85
  "then": {
79
86
  "not": { "required": ["idempotencyKey"] },
80
87
  "properties": {
88
+ "mode": false,
81
89
  "input": { "propertyNames": { "enum": ["messageId"] }, "required": ["messageId"] }
82
90
  }
83
91
  }
@@ -87,6 +95,7 @@
87
95
  "then": {
88
96
  "required": ["idempotencyKey"],
89
97
  "properties": {
98
+ "mode": false,
90
99
  "input": { "propertyNames": { "enum": ["to", "cc", "bcc", "subject", "text", "replyToMessageId"] }, "required": ["to", "subject", "text"] }
91
100
  }
92
101
  }
@@ -114,6 +123,14 @@
114
123
  "resource": "inbox",
115
124
  "input": { "to": ["jane@example.com"], "subject": "Weekly report", "text": "Shipped the weekly report.", "replyToMessageId": "18f2c3a4b5d6e7f8" },
116
125
  "idempotencyKey": "6f1c2c1e-3a1c-4c3e-9f0e-2b6f7d8a9c11"
126
+ },
127
+ {
128
+ "connection": "company-slack",
129
+ "operation": "slack.message.post",
130
+ "resource": "team-updates",
131
+ "input": { "text": "Lunch is at noon." },
132
+ "idempotencyKey": "6f1c2c1e-3a1c-4c3e-9f0e-2b6f7d8a9c12",
133
+ "mode": "user"
117
134
  }
118
135
  ]
119
136
  }
@@ -2,7 +2,7 @@
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
3
  "$id": "https://schemas.harbour.dev/integration-execute-result.schema.json",
4
4
  "title": "IntegrationExecuteResult",
5
- "description": "The `data` member of a successful execute response `{data, requestId}`; the SDK unwraps `data`. Exactly one of the six operation results: slack.channel.history -> {messages, hasMore} (plain text/IDs/time only, never the provider payload); slack.message.post -> {messageId}; gmail.thread.list -> {threads, hasMore} (newest inbox threads; each `messageId` is the thread's newest message, what gmail.message.read takes); gmail.message.read -> {id, threadId, subject, from, to, date, text, truncated} (headers plus the plain text with html stripped, cut at 16 KiB with `truncated` set); gmail.message.send -> {messageId, threadId} (the Gmail ids of the sent message and its thread); warehouse.view.read -> {rows} (decimal/64-bit values as strings, dates as ISO strings). An unknown send outcome is never a result; it is an error with details.code SEND_OUTCOME_UNKNOWN. Results over 1 MiB are rejected, not truncated.",
5
+ "description": "The `data` member of a successful execute response `{data, requestId}`; the SDK unwraps `data`. Exactly one of the six operation results: slack.channel.history -> {messages, hasMore} (plain text/IDs/time, plus the display name and emoji icon a per-app-named app post carries — authorName/authorIcon, 0.55.0 — never the provider payload); slack.message.post -> {messageId}; gmail.thread.list -> {threads, hasMore} (newest inbox threads; each `messageId` is the thread's newest message, what gmail.message.read takes); gmail.message.read -> {id, threadId, subject, from, to, date, text, truncated} (headers plus the plain text with html stripped, cut at 16 KiB with `truncated` set); gmail.message.send -> {messageId, threadId} (the Gmail ids of the sent message and its thread); warehouse.view.read -> {rows} (decimal/64-bit values as strings, dates as ISO strings). An unknown send outcome is never a result; it is an error with details.code SEND_OUTCOME_UNKNOWN. Results over 1 MiB are rejected, not truncated.",
6
6
  "type": "object",
7
7
  "additionalProperties": false,
8
8
  "properties": {
@@ -62,7 +62,9 @@
62
62
  "id": { "type": "string", "minLength": 1, "maxLength": 128 },
63
63
  "text": { "type": "string", "maxLength": 40000 },
64
64
  "authorId": { "type": "string", "minLength": 1, "maxLength": 128 },
65
- "createdAt": { "type": "string", "format": "date-time" }
65
+ "createdAt": { "type": "string", "format": "date-time" },
66
+ "authorName": { "type": "string", "minLength": 1, "maxLength": 128, "description": "The display name a per-app-named app post carries (0.55.0; Slack's `username` on a customised bot message), so an app can recognise its own posts in a channel; absent on a person's message and on a post under the bot's own name." },
67
+ "authorIcon": { "type": "string", "pattern": "^:[a-z0-9_+-]{1,60}:$", "description": "The emoji shortcode such a post carries as its icon (0.55.0), when it has one; never an image URL." }
66
68
  }
67
69
  }
68
70
  },
@@ -29,6 +29,14 @@
29
29
  "sessionFixture": { "$ref": "#/definitions/ImageDigestReference" }
30
30
  }
31
31
  },
32
+ "nativeRuntime": {
33
+ "type": "object",
34
+ "additionalProperties": false,
35
+ "description": "Native local App Gateway executables. The first release supports Apple Silicon; hosted workloads continue to use images.",
36
+ "properties": {
37
+ "darwinArm64": { "$ref": "#/definitions/NativeExecutable" }
38
+ }
39
+ },
32
40
  "brief": {
33
41
  "type": "object",
34
42
  "additionalProperties": false,
@@ -50,6 +58,15 @@
50
58
  "pattern": "^[a-z0-9][a-z0-9._/-]*(:[0-9]+)?(/[a-z0-9._-]+)*@sha256:[0-9a-f]{64}$",
51
59
  "maxLength": 512,
52
60
  "description": "Digest-pinned image reference <repository>@sha256:<hex>; tags are never accepted."
61
+ },
62
+ "NativeExecutable": {
63
+ "type": "object",
64
+ "additionalProperties": false,
65
+ "required": ["url", "sha256"],
66
+ "properties": {
67
+ "url": { "type": "string", "pattern": "^https://[^\\s{}]+$", "maxLength": 2048 },
68
+ "sha256": { "$ref": "#/definitions/HexSha256" }
69
+ }
53
70
  }
54
71
  },
55
72
  "examples": [