@fourier-labs/harbour-contracts 0.72.0 → 0.74.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 +2 -2
- package/contract-release.json +24 -24
- package/dist/gen-ts/app-integrations-declaration.d.ts +29 -20
- package/dist/gen-ts/contract-release.d.ts +6 -6
- package/dist/gen-ts/contract-release.js +6 -6
- package/dist/gen-ts/data-grant-v2.d.ts +2 -2
- package/dist/gen-ts/data-source.d.ts +19 -2
- package/dist/gen-ts/integration-error-details.d.ts +2 -2
- package/dist/gen-ts/integration-execute-input-rules.d.ts +35 -0
- package/dist/gen-ts/integration-execute-input-rules.js +15 -0
- package/dist/gen-ts/integration-execute-request.d.ts +24 -2
- package/dist/gen-ts/integration-execute-result.d.ts +9 -0
- package/dist/gen-ts/integration-operations.d.ts +7 -0
- package/dist/gen-ts/integration-operations.js +9 -0
- package/dist/gen-ts/saas-connection.d.ts +1 -1
- package/gen-ts/app-integrations-declaration.ts +29 -20
- package/gen-ts/contract-release.ts +6 -6
- package/gen-ts/data-grant-v2.ts +2 -1
- package/gen-ts/data-source.ts +19 -2
- package/gen-ts/integration-error-details.ts +3 -2
- package/gen-ts/integration-execute-input-rules.ts +15 -0
- package/gen-ts/integration-execute-request.ts +25 -2
- package/gen-ts/integration-execute-result.ts +9 -0
- package/gen-ts/integration-operations.ts +9 -0
- package/gen-ts/saas-connection.ts +1 -0
- package/package.json +1 -1
- package/schema/app-integrations-declaration.schema.json +478 -67
- package/schema/data-source.schema.json +57 -2
- package/schema/integration-error-details.schema.json +3 -2
- package/schema/integration-execute-request.schema.json +905 -120
- package/schema/integration-execute-result.schema.json +520 -82
package/gen-ts/data-grant-v2.ts
CHANGED
|
@@ -30,7 +30,7 @@ export interface DataGrantV2 {
|
|
|
30
30
|
* Whose authority an operation runs under. Fixed per operation, except the declared-mode Slack operations (slack.message.post; since 0.71.0 also slack.channel.history, slack.message.update, slack.message.delete): 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
|
-
kind: 'saas' | 'database';
|
|
33
|
+
kind: 'saas' | 'database' | 'service';
|
|
34
34
|
status: DataGrantV2Status;
|
|
35
35
|
/**
|
|
36
36
|
* Approved operations; each must match this grant's mode. Empty until the first approval. At most the eleven SaaS operations (0.71.0); a database grant holds one.
|
|
@@ -50,6 +50,7 @@ export interface DataGrantV2 {
|
|
|
50
50
|
| 'gmail.message.send'
|
|
51
51
|
| 'gmail.attachment.read'
|
|
52
52
|
| 'warehouse.view.read'
|
|
53
|
+
| 'service.request'
|
|
53
54
|
)[];
|
|
54
55
|
/**
|
|
55
56
|
* Approved logical resources. Never inherited by wildcard. Empty until the first approval.
|
package/gen-ts/data-source.ts
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
// Code generated by tools/gen/gen.mjs from schema/*.json. DO NOT EDIT.
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Closed connector set for the Data-Access MVP.
|
|
5
|
+
* Closed connector set for the Data-Access MVP. trino (0.74.0): Trino/Presto-compatible engines (Trino, Presto, Starburst, Galaxy) over the Trino HTTP statement protocol; the endpoint reuses host, port and database (as the Trino catalog) with no connector-specific fields.
|
|
6
6
|
*/
|
|
7
|
-
export type DataSourceConnector = 'postgres' | 'redshift' | 'bigquery' | 'databricks' | 'mysql';
|
|
7
|
+
export type DataSourceConnector = 'postgres' | 'redshift' | 'bigquery' | 'databricks' | 'mysql' | 'trino';
|
|
8
8
|
/**
|
|
9
9
|
* byo = admin brought existing infrastructure; harbour_managed = Isomorph provisioned it.
|
|
10
10
|
*/
|
|
@@ -54,6 +54,13 @@ export interface DataSource {
|
|
|
54
54
|
*/
|
|
55
55
|
allowValidation?: boolean;
|
|
56
56
|
status: DataSourceStatus;
|
|
57
|
+
/**
|
|
58
|
+
* 0.74.0: per-environment copies of the source. A read for an environment that has a copy here uses that copy's endpoint and credentialRef; production always uses the top-level endpoint and credentialRef. A development read with no development copy is answered from the app's sample rows (integration-execute-request.sampleRows), never from the source; a preview read with no preview copy uses the production endpoint. A laptop never reads the production endpoint.
|
|
59
|
+
*/
|
|
60
|
+
environments?: {
|
|
61
|
+
development?: DataSourceEnvironmentCopy;
|
|
62
|
+
preview?: DataSourceEnvironmentCopy;
|
|
63
|
+
};
|
|
57
64
|
}
|
|
58
65
|
/**
|
|
59
66
|
* Connector endpoint coordinates. host is always present; database vs project/workspace depends on the connector family.
|
|
@@ -80,3 +87,13 @@ export interface DataSourceEndpoint {
|
|
|
80
87
|
*/
|
|
81
88
|
workspace?: string;
|
|
82
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* One environment's copy of a source: the same endpoint and credentialRef shapes as the top-level fields, for the same connector.
|
|
92
|
+
*/
|
|
93
|
+
export interface DataSourceEnvironmentCopy {
|
|
94
|
+
endpoint: DataSourceEndpoint;
|
|
95
|
+
/**
|
|
96
|
+
* secretref URI naming the platform-held credential for this copy. The referenced value never crosses this boundary.
|
|
97
|
+
*/
|
|
98
|
+
credentialRef: string;
|
|
99
|
+
}
|
|
@@ -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. 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.
|
|
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. SAMPLE_ROWS_NOT_ALLOWED (400 VALIDATION_FAILED, 0.74.0): an execute request carried sampleRows on any route but the development one. A sample value that does not parse as the column's recorded type is INPUT_INVALID, naming the column and the row.
|
|
11
11
|
*/
|
|
12
12
|
export type IntegrationErrorCode =
|
|
13
13
|
| 'USER_CONNECTION_REQUIRED'
|
|
@@ -39,7 +39,8 @@ export type IntegrationErrorCode =
|
|
|
39
39
|
| 'AUTH_REQUIRED'
|
|
40
40
|
| 'UNAVAILABLE'
|
|
41
41
|
| 'MODE_NOT_GRANTED'
|
|
42
|
-
| 'MODE_REQUIRED'
|
|
42
|
+
| 'MODE_REQUIRED'
|
|
43
|
+
| 'SAMPLE_ROWS_NOT_ALLOWED';
|
|
43
44
|
|
|
44
45
|
/**
|
|
45
46
|
* The `error` member of an integration failure response `{error, requestId}` on the execute/connect routes; the SDK maps it onto IsomorphError(category, message, details). `category` is the app-SDK IsomorphErrorCategory (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.
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* same message wherever it is made.
|
|
10
10
|
*/
|
|
11
11
|
export type IntegrationExecuteInputFieldRule =
|
|
12
|
+
| { kind: 'object'; required: boolean }
|
|
12
13
|
| { kind: 'integer'; required: boolean; minimum: number; maximum: number; default?: number }
|
|
13
14
|
| { kind: 'string'; required: boolean; minLength: number; maxLength?: number; pattern?: string }
|
|
14
15
|
| { kind: 'string[]'; required: boolean; minItems: number; maxItems: number; uniqueItems: boolean; item: { minLength: number; maxLength?: number; pattern?: string }; default?: readonly string[] }
|
|
@@ -134,6 +135,17 @@ export const IntegrationExecuteInputRules = {
|
|
|
134
135
|
limit: { kind: "integer", required: false, minimum: 1, maximum: 1000, default: 100 },
|
|
135
136
|
},
|
|
136
137
|
},
|
|
138
|
+
"service.request": {
|
|
139
|
+
idempotencyKey: false,
|
|
140
|
+
fields: {
|
|
141
|
+
method: { kind: "enum", required: true, values: ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"] },
|
|
142
|
+
path: { kind: "string", required: true, minLength: 0, maxLength: 16384 },
|
|
143
|
+
pathParams: { kind: "object", required: false },
|
|
144
|
+
query: { kind: "object", required: false },
|
|
145
|
+
headers: { kind: "object", required: false },
|
|
146
|
+
body: { kind: "object", required: false },
|
|
147
|
+
},
|
|
148
|
+
},
|
|
137
149
|
} as const satisfies Record<string, IntegrationExecuteOperationRule>;
|
|
138
150
|
|
|
139
151
|
export type IntegrationExecuteOperation = keyof typeof IntegrationExecuteInputRules;
|
|
@@ -194,6 +206,9 @@ function checkFields(operation: string, prefix: string, fields: Readonly<Record<
|
|
|
194
206
|
|
|
195
207
|
function checkField(operation: string, path: string, name: string, field: IntegrationExecuteInputFieldRule, value: unknown): { ok: true; value: unknown } | Refusal {
|
|
196
208
|
switch (field.kind) {
|
|
209
|
+
case 'object':
|
|
210
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) return refuse(path, `${path} must be a JSON object`);
|
|
211
|
+
return { ok: true, value };
|
|
197
212
|
case 'integer':
|
|
198
213
|
if (typeof value !== 'number' || !Number.isInteger(value) || value < field.minimum || value > field.maximum) return refuse(path, `${path} must be an integer between ${field.minimum} and ${field.maximum}`);
|
|
199
214
|
return { ok: true, value };
|
|
@@ -24,11 +24,12 @@ export type IntegrationExecuteRequest = {
|
|
|
24
24
|
| 'gmail.message.read'
|
|
25
25
|
| 'gmail.message.send'
|
|
26
26
|
| 'gmail.attachment.read'
|
|
27
|
-
| 'warehouse.view.read'
|
|
27
|
+
| 'warehouse.view.read'
|
|
28
|
+
| 'service.request';
|
|
28
29
|
/**
|
|
29
30
|
* Tenant-wide logical alias of a connection (governance sourceId) or of a resource under it.
|
|
30
31
|
*/
|
|
31
|
-
resource
|
|
32
|
+
resource?: string;
|
|
32
33
|
input: {
|
|
33
34
|
/**
|
|
34
35
|
* Read operations: how many items to return; the per-operation then-block sets the real ceiling (100 for slack.channel.history, 50 for gmail.thread.list, 1000 for warehouse.view.read) and the default.
|
|
@@ -142,6 +143,20 @@ export type IntegrationExecuteRequest = {
|
|
|
142
143
|
* @maxItems 8
|
|
143
144
|
*/
|
|
144
145
|
orderBy?: WarehouseOrder[];
|
|
146
|
+
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
|
|
147
|
+
path?: string;
|
|
148
|
+
pathParams?: {
|
|
149
|
+
[k: string]: unknown;
|
|
150
|
+
};
|
|
151
|
+
query?: {
|
|
152
|
+
[k: string]: unknown;
|
|
153
|
+
};
|
|
154
|
+
headers?: {
|
|
155
|
+
[k: string]: unknown;
|
|
156
|
+
};
|
|
157
|
+
body?: {
|
|
158
|
+
[k: string]: unknown;
|
|
159
|
+
};
|
|
145
160
|
};
|
|
146
161
|
/**
|
|
147
162
|
* 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.
|
|
@@ -151,6 +166,14 @@ export type IntegrationExecuteRequest = {
|
|
|
151
166
|
* The declared-mode Slack operations only (slack.message.post since 0.55.0; slack.channel.history, slack.message.update and slack.message.delete since 0.71.0): which of the app's IT-approved modes this call runs under — app (the company's Slack app; a post goes out under the grant's per-app name and the footer) or user (the signed-in person, after their consent; no name, no footer). Required when the app holds approved grants for both modes (else MODE_REQUIRED); may be omitted when it holds one, which is then used. A named mode without an approved grant is refused with MODE_NOT_GRANTED; governance never falls back to the other mode. Every other operation's mode is fixed, so the field is refused there.
|
|
152
167
|
*/
|
|
153
168
|
mode?: 'app' | 'user';
|
|
169
|
+
/**
|
|
170
|
+
* Development only (0.74.0). Rows the kit dev runtime attaches for the resource being read, from the sample-row file the agent wrote into the app's .isomorph/ folder. Governance serves them for a development warehouse.view.read when the source has no development copy (data-source.environments.development): it parses each value by the resource's recorded column type and refuses a value that does not parse, naming the column and the row. The field is refused with SAMPLE_ROWS_NOT_ALLOWED on any route but the development one, so a preview or production read never carries made-up data. Not part of the per-operation input rules; the dev runtime adds it beside the app's request.
|
|
171
|
+
*
|
|
172
|
+
* @maxItems 50
|
|
173
|
+
*/
|
|
174
|
+
sampleRows?: {
|
|
175
|
+
[k: string]: string | number | boolean | null;
|
|
176
|
+
}[];
|
|
154
177
|
};
|
|
155
178
|
/**
|
|
156
179
|
* One bare email address (no display name).
|
|
@@ -46,6 +46,10 @@ export type IntegrationExecuteResult = {
|
|
|
46
46
|
* gmail.message.read: the text was cut at 16 KiB. warehouse.view.read (0.70.0): more rows matched than the read's limit, so `rows` is incomplete.
|
|
47
47
|
*/
|
|
48
48
|
truncated?: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* warehouse.view.read (0.74.0): true when `rows` came from the app's sample rows (integration-execute-request.sampleRows) because the development read had no development copy of the source; the rows were never read from the source. Absent or false: the rows came from the source.
|
|
51
|
+
*/
|
|
52
|
+
sample?: boolean;
|
|
49
53
|
/**
|
|
50
54
|
* gmail.message.read (0.71.0): the message's attachments, without their bytes.
|
|
51
55
|
*
|
|
@@ -74,6 +78,11 @@ export type IntegrationExecuteResult = {
|
|
|
74
78
|
rows?: {
|
|
75
79
|
[k: string]: unknown;
|
|
76
80
|
}[];
|
|
81
|
+
status?: number;
|
|
82
|
+
headers?: {
|
|
83
|
+
[k: string]: string;
|
|
84
|
+
};
|
|
85
|
+
body?: unknown;
|
|
77
86
|
};
|
|
78
87
|
|
|
79
88
|
export interface ChannelMessage {
|
|
@@ -114,5 +114,14 @@ export const IntegrationOperations = {
|
|
|
114
114
|
"readOnly": true,
|
|
115
115
|
"declaredMode": false,
|
|
116
116
|
"idempotencyKey": false
|
|
117
|
+
},
|
|
118
|
+
"service.request": {
|
|
119
|
+
"kind": "service",
|
|
120
|
+
"identities": [
|
|
121
|
+
"app"
|
|
122
|
+
],
|
|
123
|
+
"readOnly": false,
|
|
124
|
+
"declaredMode": false,
|
|
125
|
+
"idempotencyKey": false
|
|
117
126
|
}
|
|
118
127
|
} as const;
|
package/package.json
CHANGED