@fourier-labs/harbour-contracts 0.50.0 → 0.51.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 +6 -0
- package/contract-release.json +13 -10
- package/dist/gen-ts/contract-release.d.ts +6 -6
- package/dist/gen-ts/contract-release.js +6 -6
- package/dist/gen-ts/index.d.ts +1 -0
- package/dist/gen-ts/index.js +1 -0
- package/dist/gen-ts/integration-execute-input-rules.d.ts +187 -0
- package/dist/gen-ts/integration-execute-input-rules.js +114 -0
- package/dist/gen-ts/integration-execute-request.d.ts +7 -1
- package/gen-ts/contract-release.ts +6 -6
- package/gen-ts/index.ts +1 -0
- package/gen-ts/integration-execute-input-rules.ts +119 -0
- package/gen-ts/integration-execute-request.ts +7 -1
- package/package.json +1 -1
- package/schema/integration-execute-request.schema.json +9 -9
package/README.md
CHANGED
|
@@ -84,6 +84,12 @@ Two schemas carry logic beyond structural types, generated into helpers:
|
|
|
84
84
|
- **`binding-classification-subject.schema.json`** and **`sanitized-binding-evidence.schema.json`**
|
|
85
85
|
→ canonical length-prefixed SHA-256 preimage/digest helpers plus exact-name,
|
|
86
86
|
requested-mapping and allocation-receipt validators in Go and TypeScript.
|
|
87
|
+
- **`integration-execute-request.schema.json`** → `IntegrationExecuteInputRules` +
|
|
88
|
+
`validateIntegrationExecuteInput(operation, input)` (TS) / `ValidateIntegrationExecuteInput` (Go),
|
|
89
|
+
derived from each operation's `then`-block (`propertyNames.enum`, `required`, per-field
|
|
90
|
+
`maximum`/`default`). The governance execute route, the app SDK and the kit's local check all
|
|
91
|
+
refuse a request with the same sentence (`limit must be an integer between 1 and 15`); the
|
|
92
|
+
SDK vendors the TypeScript file because it ships with no dependencies.
|
|
87
93
|
|
|
88
94
|
## Workflow
|
|
89
95
|
|
package/contract-release.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": "harbour.contract-release/1.0",
|
|
3
|
-
"repositoryVersion": "0.
|
|
4
|
-
"publishVersion": "0.
|
|
5
|
-
"publishTag": "v0.
|
|
6
|
-
"schemasDigest": "sha256:
|
|
3
|
+
"repositoryVersion": "0.51.0.0",
|
|
4
|
+
"publishVersion": "0.51.0",
|
|
5
|
+
"publishTag": "v0.51.0",
|
|
6
|
+
"schemasDigest": "sha256:018bf3078087a18b07990772c94669f658159bc84b0c8accedc7dd5daa38bee5",
|
|
7
7
|
"aiFixturesDigest": "sha256:edac31c92e146ca1c40130f2fb5649db82e24b8cd1ac758153b90612052fddb6",
|
|
8
|
-
"generatedGoDigest": "sha256:
|
|
9
|
-
"generatedTypeScriptDigest": "sha256:
|
|
8
|
+
"generatedGoDigest": "sha256:98bb6999738be926c36fd8d0735a17defdad590ced57e94bc431bb9bba53c9ad",
|
|
9
|
+
"generatedTypeScriptDigest": "sha256:1d83b04e4899777f2963177e39e7ac99852cae80ffb8c03b6a294f5fb19d3600",
|
|
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)"
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"schema/error-envelope.schema.json": "sha256:75dccda7dd16fdf8435deda53fc7dbcdc109b65114617a17f11763010c656f90",
|
|
53
53
|
"schema/integration-connect-result.schema.json": "sha256:3f254b67ef739dc1fa395bdc8c4dc93359b16d481dba2a2d777aa5b6f34b7af8",
|
|
54
54
|
"schema/integration-error-details.schema.json": "sha256:09cebaddb7f88e63cbebaa1cada5f91057ae4dc4370e973ef38f7f11c6323284",
|
|
55
|
-
"schema/integration-execute-request.schema.json": "sha256:
|
|
55
|
+
"schema/integration-execute-request.schema.json": "sha256:18613bab7c233d1e2d52b960f5fe6ae4409171fa03a3aebf415cf86ae66e2866",
|
|
56
56
|
"schema/integration-execute-result.schema.json": "sha256:766146779c01ee1dbdd2161c5dc594f43ccaf322762352bb0dc5b80bcb3275b5",
|
|
57
57
|
"schema/kit-bundle-manifest.schema.json": "sha256:c15e28ae028dbd1c262427b9c797142e19b1f38fa36b4ecfe187e32c20a174ec",
|
|
58
58
|
"schema/kit-lock.schema.json": "sha256:5e24b76660f83990af7a6c037af350c3850ee439ef956f65a0360facbdcdd997",
|
|
@@ -105,8 +105,10 @@
|
|
|
105
105
|
"gen-go/contracts_test.go": "sha256:56376287cb1548be1b35ab4c65d6f0b558c5d34885f7a47cb3d9e8d786607bf5",
|
|
106
106
|
"gen-go/doc.go": "sha256:c17bc2d39ed175ce7105c68305f8ae7152f659eb7cd187ecceee7698eb3a65dd",
|
|
107
107
|
"gen-go/error_category.go": "sha256:b2ddea22dcfa030f1a45e721e635485b0bd816cf1f38e7654628a653f5ed4583",
|
|
108
|
+
"gen-go/integration_execute_input_rules.go": "sha256:224fb490a5f936b30927e6637e698093e1e42a695cbc1bcf9a39bc70db9f2172",
|
|
109
|
+
"gen-go/integration_execute_input_rules_test.go": "sha256:c437a8406a2f062a0173b9419e4cb176fc6eb54e569506225d5162323f55b40b",
|
|
108
110
|
"gen-go/schema_documents.go": "sha256:66055e0d4354f73d922ee17984de8df065db665f93182b87fcd8a6a94ae94300",
|
|
109
|
-
"gen-go/types.go": "sha256:
|
|
111
|
+
"gen-go/types.go": "sha256:c94b302d560efb7d8a8f6a4387a22a2f0321489e04d9d126d561cd921ff07eec",
|
|
110
112
|
"gen-go/version_state.go": "sha256:4ef8abb126414c0025cf2c59bc9c79a74ef4a2ff89e8f316cbf05acc872371c1"
|
|
111
113
|
},
|
|
112
114
|
"generatedTypeScript": {
|
|
@@ -152,10 +154,11 @@
|
|
|
152
154
|
"gen-ts/error-category-registry.ts": "sha256:7466a502c0723a77db825cdeaa9556a244201b55d9b7acbf2c098e9e84351df1",
|
|
153
155
|
"gen-ts/error-category.ts": "sha256:9bda89180c3b740c9aa8be466216592e8ca9d0d368c274333edf6ea5c45afd8e",
|
|
154
156
|
"gen-ts/error-envelope.ts": "sha256:4457309be794f9d4fc0cf62530cc2d442c007bac9ae84aff66df099cc9e40310",
|
|
155
|
-
"gen-ts/index.ts": "sha256:
|
|
157
|
+
"gen-ts/index.ts": "sha256:1fa78bbfd85f29f777f9018d110b70388af35ab2e428c07f75f6701ff3eaba22",
|
|
156
158
|
"gen-ts/integration-connect-result.ts": "sha256:4b10f169b6e1af364e9b418de21caa9fadbfb77b43cbff77d741cfae0b52180c",
|
|
157
159
|
"gen-ts/integration-error-details.ts": "sha256:8243f5871f376d1cab4d90e07f3151d148fb03c704168dbccdfe6c9fe76bce83",
|
|
158
|
-
"gen-ts/integration-execute-
|
|
160
|
+
"gen-ts/integration-execute-input-rules.ts": "sha256:a779c872c44bb892dec7b28173d961696d95e5b6e1fa14e2ba67dd78344cdc5b",
|
|
161
|
+
"gen-ts/integration-execute-request.ts": "sha256:06056ac8ca4a649560189ee78b40a9d5ad5ca5089d967e5708cbc082aa21ba95",
|
|
159
162
|
"gen-ts/integration-execute-result.ts": "sha256:4aaf6a9688f9dfdbc87a408556b62f822518acf5b76548076adb55393c0863b4",
|
|
160
163
|
"gen-ts/kit-bundle-manifest.ts": "sha256:9041b32af3df781ca6800bc14c3719e5e54c81dff370633454ac3ad27432eb58",
|
|
161
164
|
"gen-ts/kit-lock.ts": "sha256:de4ae82bac7bd234a3230ae671962e80eed6542d695a032a442673659dc6be5e",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export declare const ContractRepositoryVersion: "0.
|
|
2
|
-
export declare const ContractPublishVersion: "0.
|
|
3
|
-
export declare const ContractPublishTag: "v0.
|
|
4
|
-
export declare const ContractSchemasDigest: "sha256:
|
|
1
|
+
export declare const ContractRepositoryVersion: "0.51.0.0";
|
|
2
|
+
export declare const ContractPublishVersion: "0.51.0";
|
|
3
|
+
export declare const ContractPublishTag: "v0.51.0";
|
|
4
|
+
export declare const ContractSchemasDigest: "sha256:018bf3078087a18b07990772c94669f658159bc84b0c8accedc7dd5daa38bee5";
|
|
5
5
|
export declare const ContractAiFixturesDigest: "sha256:edac31c92e146ca1c40130f2fb5649db82e24b8cd1ac758153b90612052fddb6";
|
|
6
|
-
export declare const ContractGeneratedGoDigest: "sha256:
|
|
7
|
-
export declare const ContractGeneratedTypeScriptDigest: "sha256:
|
|
6
|
+
export declare const ContractGeneratedGoDigest: "sha256:98bb6999738be926c36fd8d0735a17defdad590ced57e94bc431bb9bba53c9ad";
|
|
7
|
+
export declare const ContractGeneratedTypeScriptDigest: "sha256:1d83b04e4899777f2963177e39e7ac99852cae80ffb8c03b6a294f5fb19d3600";
|
|
@@ -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.
|
|
4
|
-
export const ContractPublishVersion = "0.
|
|
5
|
-
export const ContractPublishTag = "v0.
|
|
6
|
-
export const ContractSchemasDigest = "sha256:
|
|
3
|
+
export const ContractRepositoryVersion = "0.51.0.0";
|
|
4
|
+
export const ContractPublishVersion = "0.51.0";
|
|
5
|
+
export const ContractPublishTag = "v0.51.0";
|
|
6
|
+
export const ContractSchemasDigest = "sha256:018bf3078087a18b07990772c94669f658159bc84b0c8accedc7dd5daa38bee5";
|
|
7
7
|
export const ContractAiFixturesDigest = "sha256:edac31c92e146ca1c40130f2fb5649db82e24b8cd1ac758153b90612052fddb6";
|
|
8
|
-
export const ContractGeneratedGoDigest = "sha256:
|
|
9
|
-
export const ContractGeneratedTypeScriptDigest = "sha256:
|
|
8
|
+
export const ContractGeneratedGoDigest = "sha256:98bb6999738be926c36fd8d0735a17defdad590ced57e94bc431bb9bba53c9ad";
|
|
9
|
+
export const ContractGeneratedTypeScriptDigest = "sha256:1d83b04e4899777f2963177e39e7ac99852cae80ffb8c03b6a294f5fb19d3600";
|
package/dist/gen-ts/index.d.ts
CHANGED
|
@@ -43,6 +43,7 @@ export * from './error-category.js';
|
|
|
43
43
|
export * from './error-envelope.js';
|
|
44
44
|
export * from './integration-connect-result.js';
|
|
45
45
|
export * from './integration-error-details.js';
|
|
46
|
+
export * from './integration-execute-input-rules.js';
|
|
46
47
|
export * from './integration-execute-request.js';
|
|
47
48
|
export * from './integration-execute-result.js';
|
|
48
49
|
export * from './kit-bundle-manifest.js';
|
package/dist/gen-ts/index.js
CHANGED
|
@@ -45,6 +45,7 @@ export * from './error-category.js';
|
|
|
45
45
|
export * from './error-envelope.js';
|
|
46
46
|
export * from './integration-connect-result.js';
|
|
47
47
|
export * from './integration-error-details.js';
|
|
48
|
+
export * from './integration-execute-input-rules.js';
|
|
48
49
|
export * from './integration-execute-request.js';
|
|
49
50
|
export * from './integration-execute-result.js';
|
|
50
51
|
export * from './kit-bundle-manifest.js';
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-operation input rules of integration-execute-request: derived from each
|
|
3
|
+
* operation's then-block (propertyNames.enum, required, per-field overrides).
|
|
4
|
+
* The governance execute route, the app SDK and the kit's local check validate
|
|
5
|
+
* with validateIntegrationExecuteInput below, so a request is refused with the
|
|
6
|
+
* same message wherever it is made.
|
|
7
|
+
*/
|
|
8
|
+
export type IntegrationExecuteInputFieldRule = {
|
|
9
|
+
kind: 'integer';
|
|
10
|
+
required: boolean;
|
|
11
|
+
minimum: number;
|
|
12
|
+
maximum: number;
|
|
13
|
+
default?: number;
|
|
14
|
+
} | {
|
|
15
|
+
kind: 'string';
|
|
16
|
+
required: boolean;
|
|
17
|
+
minLength: number;
|
|
18
|
+
maxLength?: number;
|
|
19
|
+
pattern?: string;
|
|
20
|
+
} | {
|
|
21
|
+
kind: 'string[]';
|
|
22
|
+
required: boolean;
|
|
23
|
+
minItems: number;
|
|
24
|
+
maxItems: number;
|
|
25
|
+
uniqueItems: boolean;
|
|
26
|
+
item: {
|
|
27
|
+
minLength: number;
|
|
28
|
+
maxLength?: number;
|
|
29
|
+
pattern?: string;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
export type IntegrationExecuteOperationRule = {
|
|
33
|
+
idempotencyKey: boolean;
|
|
34
|
+
fields: Record<string, IntegrationExecuteInputFieldRule>;
|
|
35
|
+
};
|
|
36
|
+
export declare const IntegrationExecuteInputRules: {
|
|
37
|
+
readonly "slack.channel.history": {
|
|
38
|
+
readonly idempotencyKey: false;
|
|
39
|
+
readonly fields: {
|
|
40
|
+
readonly limit: {
|
|
41
|
+
readonly kind: "integer";
|
|
42
|
+
readonly required: false;
|
|
43
|
+
readonly minimum: 1;
|
|
44
|
+
readonly maximum: 15;
|
|
45
|
+
readonly default: 15;
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
readonly "slack.message.post": {
|
|
50
|
+
readonly idempotencyKey: true;
|
|
51
|
+
readonly fields: {
|
|
52
|
+
readonly text: {
|
|
53
|
+
readonly kind: "string";
|
|
54
|
+
readonly required: true;
|
|
55
|
+
readonly minLength: 1;
|
|
56
|
+
readonly maxLength: 4000;
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
readonly "gmail.thread.list": {
|
|
61
|
+
readonly idempotencyKey: false;
|
|
62
|
+
readonly fields: {
|
|
63
|
+
readonly limit: {
|
|
64
|
+
readonly kind: "integer";
|
|
65
|
+
readonly required: false;
|
|
66
|
+
readonly minimum: 1;
|
|
67
|
+
readonly maximum: 15;
|
|
68
|
+
readonly default: 15;
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
readonly "gmail.message.read": {
|
|
73
|
+
readonly idempotencyKey: false;
|
|
74
|
+
readonly fields: {
|
|
75
|
+
readonly messageId: {
|
|
76
|
+
readonly kind: "string";
|
|
77
|
+
readonly required: true;
|
|
78
|
+
readonly minLength: 0;
|
|
79
|
+
readonly pattern: "^[A-Za-z0-9_-]{1,64}$";
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
readonly "gmail.message.send": {
|
|
84
|
+
readonly idempotencyKey: true;
|
|
85
|
+
readonly fields: {
|
|
86
|
+
readonly to: {
|
|
87
|
+
readonly kind: "string[]";
|
|
88
|
+
readonly required: true;
|
|
89
|
+
readonly minItems: 1;
|
|
90
|
+
readonly maxItems: 50;
|
|
91
|
+
readonly uniqueItems: true;
|
|
92
|
+
readonly item: {
|
|
93
|
+
readonly minLength: 3;
|
|
94
|
+
readonly maxLength: 254;
|
|
95
|
+
readonly pattern: "^[^\\s@<>,;\"]+@[^\\s@<>,;\"]+\\.[^\\s@<>,;\"]+$";
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
readonly cc: {
|
|
99
|
+
readonly kind: "string[]";
|
|
100
|
+
readonly required: false;
|
|
101
|
+
readonly minItems: 1;
|
|
102
|
+
readonly maxItems: 50;
|
|
103
|
+
readonly uniqueItems: true;
|
|
104
|
+
readonly item: {
|
|
105
|
+
readonly minLength: 3;
|
|
106
|
+
readonly maxLength: 254;
|
|
107
|
+
readonly pattern: "^[^\\s@<>,;\"]+@[^\\s@<>,;\"]+\\.[^\\s@<>,;\"]+$";
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
readonly bcc: {
|
|
111
|
+
readonly kind: "string[]";
|
|
112
|
+
readonly required: false;
|
|
113
|
+
readonly minItems: 1;
|
|
114
|
+
readonly maxItems: 50;
|
|
115
|
+
readonly uniqueItems: true;
|
|
116
|
+
readonly item: {
|
|
117
|
+
readonly minLength: 3;
|
|
118
|
+
readonly maxLength: 254;
|
|
119
|
+
readonly pattern: "^[^\\s@<>,;\"]+@[^\\s@<>,;\"]+\\.[^\\s@<>,;\"]+$";
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
readonly subject: {
|
|
123
|
+
readonly kind: "string";
|
|
124
|
+
readonly required: true;
|
|
125
|
+
readonly minLength: 1;
|
|
126
|
+
readonly maxLength: 256;
|
|
127
|
+
};
|
|
128
|
+
readonly text: {
|
|
129
|
+
readonly kind: "string";
|
|
130
|
+
readonly required: true;
|
|
131
|
+
readonly minLength: 1;
|
|
132
|
+
readonly maxLength: 4000;
|
|
133
|
+
};
|
|
134
|
+
readonly replyToMessageId: {
|
|
135
|
+
readonly kind: "string";
|
|
136
|
+
readonly required: false;
|
|
137
|
+
readonly minLength: 0;
|
|
138
|
+
readonly pattern: "^[A-Za-z0-9_-]{1,64}$";
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
};
|
|
142
|
+
readonly "warehouse.view.read": {
|
|
143
|
+
readonly idempotencyKey: false;
|
|
144
|
+
readonly fields: {
|
|
145
|
+
readonly columns: {
|
|
146
|
+
readonly kind: "string[]";
|
|
147
|
+
readonly required: true;
|
|
148
|
+
readonly minItems: 1;
|
|
149
|
+
readonly maxItems: 32;
|
|
150
|
+
readonly uniqueItems: false;
|
|
151
|
+
readonly item: {
|
|
152
|
+
readonly minLength: 0;
|
|
153
|
+
readonly pattern: "^[A-Za-z_][A-Za-z0-9_]{0,62}$";
|
|
154
|
+
};
|
|
155
|
+
};
|
|
156
|
+
readonly limit: {
|
|
157
|
+
readonly kind: "integer";
|
|
158
|
+
readonly required: false;
|
|
159
|
+
readonly minimum: 1;
|
|
160
|
+
readonly maximum: 1000;
|
|
161
|
+
readonly default: 100;
|
|
162
|
+
};
|
|
163
|
+
};
|
|
164
|
+
};
|
|
165
|
+
};
|
|
166
|
+
export type IntegrationExecuteOperation = keyof typeof IntegrationExecuteInputRules;
|
|
167
|
+
/** The upper bound of one integer input as a literal type: integrationExecuteInputMaximum('slack.channel.history', 'limit') is 15. */
|
|
168
|
+
export declare function integrationExecuteInputMaximum<O extends IntegrationExecuteOperation, F extends keyof (typeof IntegrationExecuteInputRules)[O]['fields']>(operation: O, field: F): Extract<(typeof IntegrationExecuteInputRules)[O]['fields'][F], {
|
|
169
|
+
maximum: number;
|
|
170
|
+
}>['maximum'];
|
|
171
|
+
export type IntegrationExecuteInputProblem = {
|
|
172
|
+
field: string;
|
|
173
|
+
message: string;
|
|
174
|
+
};
|
|
175
|
+
export type IntegrationExecuteInputValidation = {
|
|
176
|
+
ok: true;
|
|
177
|
+
input: Record<string, unknown>;
|
|
178
|
+
} | {
|
|
179
|
+
ok: false;
|
|
180
|
+
problem: IntegrationExecuteInputProblem;
|
|
181
|
+
};
|
|
182
|
+
/**
|
|
183
|
+
* Validates one operation's input against the contract and returns it with the
|
|
184
|
+
* contract's defaults applied. The message names the field and its allowed
|
|
185
|
+
* range; it is the sentence the platform answers with, so a caller may show it.
|
|
186
|
+
*/
|
|
187
|
+
export declare function validateIntegrationExecuteInput(operation: string, input: unknown): IntegrationExecuteInputValidation;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
// Code generated by tools/gen/gen.mjs from schema/*.json. DO NOT EDIT.
|
|
3
|
+
export const IntegrationExecuteInputRules = {
|
|
4
|
+
"slack.channel.history": {
|
|
5
|
+
idempotencyKey: false,
|
|
6
|
+
fields: {
|
|
7
|
+
limit: { kind: "integer", required: false, minimum: 1, maximum: 15, default: 15 },
|
|
8
|
+
},
|
|
9
|
+
},
|
|
10
|
+
"slack.message.post": {
|
|
11
|
+
idempotencyKey: true,
|
|
12
|
+
fields: {
|
|
13
|
+
text: { kind: "string", required: true, minLength: 1, maxLength: 4000 },
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
"gmail.thread.list": {
|
|
17
|
+
idempotencyKey: false,
|
|
18
|
+
fields: {
|
|
19
|
+
limit: { kind: "integer", required: false, minimum: 1, maximum: 15, default: 15 },
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
"gmail.message.read": {
|
|
23
|
+
idempotencyKey: false,
|
|
24
|
+
fields: {
|
|
25
|
+
messageId: { kind: "string", required: true, minLength: 0, pattern: "^[A-Za-z0-9_-]{1,64}$" },
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
"gmail.message.send": {
|
|
29
|
+
idempotencyKey: true,
|
|
30
|
+
fields: {
|
|
31
|
+
to: { kind: "string[]", required: true, minItems: 1, maxItems: 50, uniqueItems: true, item: { minLength: 3, maxLength: 254, pattern: "^[^\\s@<>,;\"]+@[^\\s@<>,;\"]+\\.[^\\s@<>,;\"]+$" } },
|
|
32
|
+
cc: { kind: "string[]", required: false, minItems: 1, maxItems: 50, uniqueItems: true, item: { minLength: 3, maxLength: 254, pattern: "^[^\\s@<>,;\"]+@[^\\s@<>,;\"]+\\.[^\\s@<>,;\"]+$" } },
|
|
33
|
+
bcc: { kind: "string[]", required: false, minItems: 1, maxItems: 50, uniqueItems: true, item: { minLength: 3, maxLength: 254, pattern: "^[^\\s@<>,;\"]+@[^\\s@<>,;\"]+\\.[^\\s@<>,;\"]+$" } },
|
|
34
|
+
subject: { kind: "string", required: true, minLength: 1, maxLength: 256 },
|
|
35
|
+
text: { kind: "string", required: true, minLength: 1, maxLength: 4000 },
|
|
36
|
+
replyToMessageId: { kind: "string", required: false, minLength: 0, pattern: "^[A-Za-z0-9_-]{1,64}$" },
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
"warehouse.view.read": {
|
|
40
|
+
idempotencyKey: false,
|
|
41
|
+
fields: {
|
|
42
|
+
columns: { kind: "string[]", required: true, minItems: 1, maxItems: 32, uniqueItems: false, item: { minLength: 0, pattern: "^[A-Za-z_][A-Za-z0-9_]{0,62}$" } },
|
|
43
|
+
limit: { kind: "integer", required: false, minimum: 1, maximum: 1000, default: 100 },
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
/** The upper bound of one integer input as a literal type: integrationExecuteInputMaximum('slack.channel.history', 'limit') is 15. */
|
|
48
|
+
export function integrationExecuteInputMaximum(operation, field) {
|
|
49
|
+
const rule = IntegrationExecuteInputRules[operation].fields[field];
|
|
50
|
+
if (!rule || rule.kind !== 'integer')
|
|
51
|
+
throw new Error(`${String(operation)}.input.${String(field)} has no integer bound`);
|
|
52
|
+
return rule.maximum;
|
|
53
|
+
}
|
|
54
|
+
const patterns = new Map();
|
|
55
|
+
function matches(pattern, value) {
|
|
56
|
+
let compiled = patterns.get(pattern);
|
|
57
|
+
if (!compiled) {
|
|
58
|
+
compiled = new RegExp(pattern, 'u');
|
|
59
|
+
patterns.set(pattern, compiled);
|
|
60
|
+
}
|
|
61
|
+
return compiled.test(value);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Validates one operation's input against the contract and returns it with the
|
|
65
|
+
* contract's defaults applied. The message names the field and its allowed
|
|
66
|
+
* range; it is the sentence the platform answers with, so a caller may show it.
|
|
67
|
+
*/
|
|
68
|
+
export function validateIntegrationExecuteInput(operation, input) {
|
|
69
|
+
const rule = IntegrationExecuteInputRules[operation];
|
|
70
|
+
if (!rule)
|
|
71
|
+
return { ok: false, problem: { field: 'operation', message: `${operation} is not a declared integration operation` } };
|
|
72
|
+
const raw = input === undefined ? {} : input;
|
|
73
|
+
if (raw === null || typeof raw !== 'object' || Array.isArray(raw))
|
|
74
|
+
return { ok: false, problem: { field: 'input', message: 'input must be an object' } };
|
|
75
|
+
const given = raw;
|
|
76
|
+
for (const name of Object.keys(given))
|
|
77
|
+
if (!Object.prototype.hasOwnProperty.call(rule.fields, name))
|
|
78
|
+
return { ok: false, problem: { field: name, message: `${name} is not an input of ${operation}` } };
|
|
79
|
+
const out = {};
|
|
80
|
+
for (const [name, field] of Object.entries(rule.fields)) {
|
|
81
|
+
const value = given[name];
|
|
82
|
+
if (value === undefined || value === null) {
|
|
83
|
+
if (field.required)
|
|
84
|
+
return { ok: false, problem: { field: name, message: `${name} is required for ${operation}` } };
|
|
85
|
+
if (field.kind === 'integer' && field.default !== undefined)
|
|
86
|
+
out[name] = field.default;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (field.kind === 'integer') {
|
|
90
|
+
if (typeof value !== 'number' || !Number.isInteger(value) || value < field.minimum || value > field.maximum)
|
|
91
|
+
return { ok: false, problem: { field: name, message: `${name} must be an integer between ${field.minimum} and ${field.maximum}` } };
|
|
92
|
+
}
|
|
93
|
+
else if (field.kind === 'string') {
|
|
94
|
+
if (typeof value !== 'string' || value.length < field.minLength || (field.maxLength !== undefined && value.length > field.maxLength))
|
|
95
|
+
return { ok: false, problem: { field: name, message: field.maxLength !== undefined ? `${name} must be ${field.minLength} to ${field.maxLength} characters` : `${name} must be a string of at least ${field.minLength} characters` } };
|
|
96
|
+
if (field.pattern && !matches(field.pattern, value))
|
|
97
|
+
return { ok: false, problem: { field: name, message: `${name} must match ${field.pattern}` } };
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
if (!Array.isArray(value) || value.length < field.minItems || value.length > field.maxItems)
|
|
101
|
+
return { ok: false, problem: { field: name, message: `${name} must list ${field.minItems} to ${field.maxItems} items` } };
|
|
102
|
+
const seen = new Set();
|
|
103
|
+
for (const [index, item] of value.entries()) {
|
|
104
|
+
if (typeof item !== 'string' || item.length < field.item.minLength || (field.item.maxLength !== undefined && item.length > field.item.maxLength) || (field.item.pattern !== undefined && !matches(field.item.pattern, item)))
|
|
105
|
+
return { ok: false, problem: { field: name, message: `${name}[${index}] is not a valid ${name} entry` } };
|
|
106
|
+
if (field.uniqueItems && seen.has(item))
|
|
107
|
+
return { ok: false, problem: { field: name, message: `${name} must not repeat an entry` } };
|
|
108
|
+
seen.add(item);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
out[name] = value;
|
|
112
|
+
}
|
|
113
|
+
return { ok: true, input: out };
|
|
114
|
+
}
|
|
@@ -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 and enforced from the approved grant; any identity/userId/scope field is rejected by additionalProperties. 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}. 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 NOT a caller field: it is fixed by the operation and enforced from the approved grant; any identity/userId/scope field is rejected by additionalProperties. 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
|
/**
|
|
@@ -15,7 +15,13 @@ export type IntegrationExecuteRequest = {
|
|
|
15
15
|
*/
|
|
16
16
|
resource: string;
|
|
17
17
|
input: {
|
|
18
|
+
/**
|
|
19
|
+
* Read operations: how many items to return; the per-operation then-block sets the real ceiling (15 for slack.channel.history and gmail.thread.list, 1000 for warehouse.view.read) and the default.
|
|
20
|
+
*/
|
|
18
21
|
limit?: number;
|
|
22
|
+
/**
|
|
23
|
+
* slack.message.post / gmail.message.send: the plain-text body, 1..4000 characters.
|
|
24
|
+
*/
|
|
19
25
|
text?: string;
|
|
20
26
|
/**
|
|
21
27
|
* gmail.message.read: the Gmail message id a gmail.thread.list result named.
|
|
@@ -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.
|
|
5
|
-
export const ContractPublishVersion = "0.
|
|
6
|
-
export const ContractPublishTag = "v0.
|
|
7
|
-
export const ContractSchemasDigest = "sha256:
|
|
4
|
+
export const ContractRepositoryVersion = "0.51.0.0" as const;
|
|
5
|
+
export const ContractPublishVersion = "0.51.0" as const;
|
|
6
|
+
export const ContractPublishTag = "v0.51.0" as const;
|
|
7
|
+
export const ContractSchemasDigest = "sha256:018bf3078087a18b07990772c94669f658159bc84b0c8accedc7dd5daa38bee5" as const;
|
|
8
8
|
export const ContractAiFixturesDigest = "sha256:edac31c92e146ca1c40130f2fb5649db82e24b8cd1ac758153b90612052fddb6" as const;
|
|
9
|
-
export const ContractGeneratedGoDigest = "sha256:
|
|
10
|
-
export const ContractGeneratedTypeScriptDigest = "sha256:
|
|
9
|
+
export const ContractGeneratedGoDigest = "sha256:98bb6999738be926c36fd8d0735a17defdad590ced57e94bc431bb9bba53c9ad" as const;
|
|
10
|
+
export const ContractGeneratedTypeScriptDigest = "sha256:1d83b04e4899777f2963177e39e7ac99852cae80ffb8c03b6a294f5fb19d3600" as const;
|
package/gen-ts/index.ts
CHANGED
|
@@ -46,6 +46,7 @@ export * from './error-category.js';
|
|
|
46
46
|
export * from './error-envelope.js';
|
|
47
47
|
export * from './integration-connect-result.js';
|
|
48
48
|
export * from './integration-error-details.js';
|
|
49
|
+
export * from './integration-execute-input-rules.js';
|
|
49
50
|
export * from './integration-execute-request.js';
|
|
50
51
|
export * from './integration-execute-result.js';
|
|
51
52
|
export * from './kit-bundle-manifest.js';
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
// Code generated by tools/gen/gen.mjs from schema/*.json. DO NOT EDIT.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Per-operation input rules of integration-execute-request: derived from each
|
|
6
|
+
* operation's then-block (propertyNames.enum, required, per-field overrides).
|
|
7
|
+
* The governance execute route, the app SDK and the kit's local check validate
|
|
8
|
+
* with validateIntegrationExecuteInput below, so a request is refused with the
|
|
9
|
+
* same message wherever it is made.
|
|
10
|
+
*/
|
|
11
|
+
export type IntegrationExecuteInputFieldRule =
|
|
12
|
+
| { kind: 'integer'; required: boolean; minimum: number; maximum: number; default?: number }
|
|
13
|
+
| { kind: 'string'; required: boolean; minLength: number; maxLength?: number; pattern?: string }
|
|
14
|
+
| { kind: 'string[]'; required: boolean; minItems: number; maxItems: number; uniqueItems: boolean; item: { minLength: number; maxLength?: number; pattern?: string } };
|
|
15
|
+
|
|
16
|
+
export type IntegrationExecuteOperationRule = { idempotencyKey: boolean; fields: Record<string, IntegrationExecuteInputFieldRule> };
|
|
17
|
+
|
|
18
|
+
export const IntegrationExecuteInputRules = {
|
|
19
|
+
"slack.channel.history": {
|
|
20
|
+
idempotencyKey: false,
|
|
21
|
+
fields: {
|
|
22
|
+
limit: { kind: "integer", required: false, minimum: 1, maximum: 15, default: 15 },
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
"slack.message.post": {
|
|
26
|
+
idempotencyKey: true,
|
|
27
|
+
fields: {
|
|
28
|
+
text: { kind: "string", required: true, minLength: 1, maxLength: 4000 },
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
"gmail.thread.list": {
|
|
32
|
+
idempotencyKey: false,
|
|
33
|
+
fields: {
|
|
34
|
+
limit: { kind: "integer", required: false, minimum: 1, maximum: 15, default: 15 },
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
"gmail.message.read": {
|
|
38
|
+
idempotencyKey: false,
|
|
39
|
+
fields: {
|
|
40
|
+
messageId: { kind: "string", required: true, minLength: 0, pattern: "^[A-Za-z0-9_-]{1,64}$" },
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
"gmail.message.send": {
|
|
44
|
+
idempotencyKey: true,
|
|
45
|
+
fields: {
|
|
46
|
+
to: { kind: "string[]", required: true, minItems: 1, maxItems: 50, uniqueItems: true, item: { minLength: 3, maxLength: 254, pattern: "^[^\\s@<>,;\"]+@[^\\s@<>,;\"]+\\.[^\\s@<>,;\"]+$" } },
|
|
47
|
+
cc: { kind: "string[]", required: false, minItems: 1, maxItems: 50, uniqueItems: true, item: { minLength: 3, maxLength: 254, pattern: "^[^\\s@<>,;\"]+@[^\\s@<>,;\"]+\\.[^\\s@<>,;\"]+$" } },
|
|
48
|
+
bcc: { kind: "string[]", required: false, minItems: 1, maxItems: 50, uniqueItems: true, item: { minLength: 3, maxLength: 254, pattern: "^[^\\s@<>,;\"]+@[^\\s@<>,;\"]+\\.[^\\s@<>,;\"]+$" } },
|
|
49
|
+
subject: { kind: "string", required: true, minLength: 1, maxLength: 256 },
|
|
50
|
+
text: { kind: "string", required: true, minLength: 1, maxLength: 4000 },
|
|
51
|
+
replyToMessageId: { kind: "string", required: false, minLength: 0, pattern: "^[A-Za-z0-9_-]{1,64}$" },
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
"warehouse.view.read": {
|
|
55
|
+
idempotencyKey: false,
|
|
56
|
+
fields: {
|
|
57
|
+
columns: { kind: "string[]", required: true, minItems: 1, maxItems: 32, uniqueItems: false, item: { minLength: 0, pattern: "^[A-Za-z_][A-Za-z0-9_]{0,62}$" } },
|
|
58
|
+
limit: { kind: "integer", required: false, minimum: 1, maximum: 1000, default: 100 },
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
} as const satisfies Record<string, IntegrationExecuteOperationRule>;
|
|
62
|
+
|
|
63
|
+
export type IntegrationExecuteOperation = keyof typeof IntegrationExecuteInputRules;
|
|
64
|
+
|
|
65
|
+
/** The upper bound of one integer input as a literal type: integrationExecuteInputMaximum('slack.channel.history', 'limit') is 15. */
|
|
66
|
+
export function integrationExecuteInputMaximum<O extends IntegrationExecuteOperation, F extends keyof (typeof IntegrationExecuteInputRules)[O]['fields']>(operation: O, field: F): Extract<(typeof IntegrationExecuteInputRules)[O]['fields'][F], { maximum: number }>['maximum'] {
|
|
67
|
+
const rule = (IntegrationExecuteInputRules[operation].fields as Record<string, IntegrationExecuteInputFieldRule>)[field as string];
|
|
68
|
+
if (!rule || rule.kind !== 'integer') throw new Error(`${String(operation)}.input.${String(field)} has no integer bound`);
|
|
69
|
+
return rule.maximum as never;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export type IntegrationExecuteInputProblem = { field: string; message: string };
|
|
73
|
+
export type IntegrationExecuteInputValidation = { ok: true; input: Record<string, unknown> } | { ok: false; problem: IntegrationExecuteInputProblem };
|
|
74
|
+
|
|
75
|
+
const patterns = new Map<string, RegExp>();
|
|
76
|
+
function matches(pattern: string, value: string): boolean {
|
|
77
|
+
let compiled = patterns.get(pattern);
|
|
78
|
+
if (!compiled) { compiled = new RegExp(pattern, 'u'); patterns.set(pattern, compiled); }
|
|
79
|
+
return compiled.test(value);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Validates one operation's input against the contract and returns it with the
|
|
84
|
+
* contract's defaults applied. The message names the field and its allowed
|
|
85
|
+
* range; it is the sentence the platform answers with, so a caller may show it.
|
|
86
|
+
*/
|
|
87
|
+
export function validateIntegrationExecuteInput(operation: string, input: unknown): IntegrationExecuteInputValidation {
|
|
88
|
+
const rule = (IntegrationExecuteInputRules as Record<string, IntegrationExecuteOperationRule>)[operation];
|
|
89
|
+
if (!rule) return { ok: false, problem: { field: 'operation', message: `${operation} is not a declared integration operation` } };
|
|
90
|
+
const raw = input === undefined ? {} : input;
|
|
91
|
+
if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) return { ok: false, problem: { field: 'input', message: 'input must be an object' } };
|
|
92
|
+
const given = raw as Record<string, unknown>;
|
|
93
|
+
for (const name of Object.keys(given)) if (!Object.prototype.hasOwnProperty.call(rule.fields, name)) return { ok: false, problem: { field: name, message: `${name} is not an input of ${operation}` } };
|
|
94
|
+
const out: Record<string, unknown> = {};
|
|
95
|
+
for (const [name, field] of Object.entries(rule.fields)) {
|
|
96
|
+
const value = given[name];
|
|
97
|
+
if (value === undefined || value === null) {
|
|
98
|
+
if (field.required) return { ok: false, problem: { field: name, message: `${name} is required for ${operation}` } };
|
|
99
|
+
if (field.kind === 'integer' && field.default !== undefined) out[name] = field.default;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (field.kind === 'integer') {
|
|
103
|
+
if (typeof value !== 'number' || !Number.isInteger(value) || value < field.minimum || value > field.maximum) return { ok: false, problem: { field: name, message: `${name} must be an integer between ${field.minimum} and ${field.maximum}` } };
|
|
104
|
+
} else if (field.kind === 'string') {
|
|
105
|
+
if (typeof value !== 'string' || value.length < field.minLength || (field.maxLength !== undefined && value.length > field.maxLength)) return { ok: false, problem: { field: name, message: field.maxLength !== undefined ? `${name} must be ${field.minLength} to ${field.maxLength} characters` : `${name} must be a string of at least ${field.minLength} characters` } };
|
|
106
|
+
if (field.pattern && !matches(field.pattern, value)) return { ok: false, problem: { field: name, message: `${name} must match ${field.pattern}` } };
|
|
107
|
+
} else {
|
|
108
|
+
if (!Array.isArray(value) || value.length < field.minItems || value.length > field.maxItems) return { ok: false, problem: { field: name, message: `${name} must list ${field.minItems} to ${field.maxItems} items` } };
|
|
109
|
+
const seen = new Set<string>();
|
|
110
|
+
for (const [index, item] of value.entries()) {
|
|
111
|
+
if (typeof item !== 'string' || item.length < field.item.minLength || (field.item.maxLength !== undefined && item.length > field.item.maxLength) || (field.item.pattern !== undefined && !matches(field.item.pattern, item))) return { ok: false, problem: { field: name, message: `${name}[${index}] is not a valid ${name} entry` } };
|
|
112
|
+
if (field.uniqueItems && seen.has(item)) return { ok: false, problem: { field: name, message: `${name} must not repeat an entry` } };
|
|
113
|
+
seen.add(item);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
out[name] = value;
|
|
117
|
+
}
|
|
118
|
+
return { ok: true, input: out };
|
|
119
|
+
}
|
|
@@ -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 and enforced from the approved grant; any identity/userId/scope field is rejected by additionalProperties. 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}. 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 NOT a caller field: it is fixed by the operation and enforced from the approved grant; any identity/userId/scope field is rejected by additionalProperties. 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
|
/**
|
|
@@ -24,7 +24,13 @@ export type IntegrationExecuteRequest = {
|
|
|
24
24
|
*/
|
|
25
25
|
resource: string;
|
|
26
26
|
input: {
|
|
27
|
+
/**
|
|
28
|
+
* Read operations: how many items to return; the per-operation then-block sets the real ceiling (15 for slack.channel.history and gmail.thread.list, 1000 for warehouse.view.read) and the default.
|
|
29
|
+
*/
|
|
27
30
|
limit?: number;
|
|
31
|
+
/**
|
|
32
|
+
* slack.message.post / gmail.message.send: the plain-text body, 1..4000 characters.
|
|
33
|
+
*/
|
|
28
34
|
text?: string;
|
|
29
35
|
/**
|
|
30
36
|
* gmail.message.read: the Gmail message id a gmail.thread.list result named.
|
package/package.json
CHANGED
|
@@ -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 and enforced from the approved grant; any identity/userId/scope field is rejected by additionalProperties. 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}. 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 NOT a caller field: it is fixed by the operation and enforced from the approved grant; any identity/userId/scope field is rejected by additionalProperties. 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"],
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
"type": "object",
|
|
15
15
|
"additionalProperties": false,
|
|
16
16
|
"properties": {
|
|
17
|
-
"limit": { "type": "integer", "minimum": 1, "maximum": 1000 },
|
|
18
|
-
"text": { "type": "string", "minLength": 1, "maxLength": 4000 },
|
|
17
|
+
"limit": { "type": "integer", "minimum": 1, "maximum": 1000, "description": "Read operations: how many items to return; the per-operation then-block sets the real ceiling (15 for slack.channel.history and gmail.thread.list, 1000 for warehouse.view.read) and the default." },
|
|
18
|
+
"text": { "type": "string", "minLength": 1, "maxLength": 4000, "description": "slack.message.post / gmail.message.send: the plain-text body, 1..4000 characters." },
|
|
19
19
|
"messageId": { "type": "string", "pattern": "^[A-Za-z0-9_-]{1,64}$", "description": "gmail.message.read: the Gmail message id a gmail.thread.list result named." },
|
|
20
20
|
"to": { "$ref": "#/definitions/EmailAddressList", "description": "gmail.message.send: primary recipients." },
|
|
21
21
|
"cc": { "$ref": "#/definitions/EmailAddressList", "description": "gmail.message.send: carbon-copy recipients." },
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"then": {
|
|
43
43
|
"not": { "required": ["idempotencyKey"] },
|
|
44
44
|
"properties": {
|
|
45
|
-
"input": { "
|
|
45
|
+
"input": { "propertyNames": { "enum": ["limit"] }, "properties": { "limit": { "maximum": 15, "default": 15 } } }
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
},
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"then": {
|
|
52
52
|
"required": ["idempotencyKey"],
|
|
53
53
|
"properties": {
|
|
54
|
-
"input": { "
|
|
54
|
+
"input": { "propertyNames": { "enum": ["text"] }, "required": ["text"] }
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
},
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"then": {
|
|
61
61
|
"not": { "required": ["idempotencyKey"] },
|
|
62
62
|
"properties": {
|
|
63
|
-
"input": { "
|
|
63
|
+
"input": { "propertyNames": { "enum": ["columns", "limit"] }, "required": ["columns"], "properties": { "limit": { "maximum": 1000, "default": 100 } } }
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
},
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"then": {
|
|
70
70
|
"not": { "required": ["idempotencyKey"] },
|
|
71
71
|
"properties": {
|
|
72
|
-
"input": { "
|
|
72
|
+
"input": { "propertyNames": { "enum": ["limit"] }, "properties": { "limit": { "maximum": 15, "default": 15 } } }
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
},
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"then": {
|
|
79
79
|
"not": { "required": ["idempotencyKey"] },
|
|
80
80
|
"properties": {
|
|
81
|
-
"input": { "
|
|
81
|
+
"input": { "propertyNames": { "enum": ["messageId"] }, "required": ["messageId"] }
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
},
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"then": {
|
|
88
88
|
"required": ["idempotencyKey"],
|
|
89
89
|
"properties": {
|
|
90
|
-
"input": { "
|
|
90
|
+
"input": { "propertyNames": { "enum": ["to", "cc", "bcc", "subject", "text", "replyToMessageId"] }, "required": ["to", "subject", "text"] }
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
}
|