@fourier-labs/harbour-contracts 0.44.0 → 0.46.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 +4 -0
- package/contract-release.json +26 -8
- package/dist/gen-ts/app-integrations-declaration.d.ts +48 -0
- package/dist/gen-ts/app-integrations-declaration.js +3 -0
- 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 +84 -0
- package/dist/gen-ts/data-grant-v2.js +13 -0
- package/dist/gen-ts/index.d.ts +9 -0
- package/dist/gen-ts/index.js +9 -0
- package/dist/gen-ts/integration-connect-result.d.ts +14 -0
- package/dist/gen-ts/integration-connect-result.js +3 -0
- package/dist/gen-ts/integration-error-details.d.ts +22 -0
- package/dist/gen-ts/integration-error-details.js +3 -0
- package/dist/gen-ts/integration-execute-request.d.ts +30 -0
- package/dist/gen-ts/integration-execute-request.js +3 -0
- package/dist/gen-ts/integration-execute-result.d.ts +23 -0
- package/dist/gen-ts/integration-execute-result.js +3 -0
- package/dist/gen-ts/kit-bundle-manifest.d.ts +40 -0
- package/dist/gen-ts/kit-bundle-manifest.js +3 -0
- package/dist/gen-ts/kit-lock.d.ts +54 -0
- package/dist/gen-ts/kit-lock.js +3 -0
- package/dist/gen-ts/saas-connection.d.ts +73 -0
- package/dist/gen-ts/saas-connection.js +3 -0
- package/gen-ts/app-integrations-declaration.ts +52 -0
- package/gen-ts/contract-release.ts +6 -6
- package/gen-ts/data-grant-v2.ts +98 -0
- package/gen-ts/index.ts +9 -0
- package/gen-ts/integration-connect-result.ts +21 -0
- package/gen-ts/integration-error-details.ts +47 -0
- package/gen-ts/integration-execute-request.ts +33 -0
- package/gen-ts/integration-execute-result.ts +27 -0
- package/gen-ts/kit-bundle-manifest.ts +44 -0
- package/gen-ts/kit-lock.ts +56 -0
- package/gen-ts/saas-connection.ts +77 -0
- package/package.json +1 -1
- package/schema/app-integrations-declaration.schema.json +144 -0
- package/schema/data-grant-v2.schema.json +108 -0
- package/schema/integration-connect-result.schema.json +24 -0
- package/schema/integration-error-details.schema.json +64 -0
- package/schema/integration-execute-request.schema.json +73 -0
- package/schema/integration-execute-result.schema.json +48 -0
- package/schema/kit-bundle-manifest.schema.json +73 -0
- package/schema/kit-lock.schema.json +37 -0
- package/schema/saas-connection.schema.json +115 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://schemas.harbour.dev/app-integrations-declaration.schema.json",
|
|
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 (three operations) and each operation carries a fixed identity mode: slack.channel.history=user, slack.message.post=app, warehouse.view.read=app. A `saas` connection may declare only the Slack operations and a `database` connection only warehouse.view.read. This file also owns the shared IntegrationOperation / IntegrationIdentityMode / IntegrationEnvironment / IntegrationConnectionKind enums referenced by the other integration schemas.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["schema", "connections"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"schema": { "type": "string", "const": "harbour.app-integrations/2.0" },
|
|
11
|
+
"connections": {
|
|
12
|
+
"type": "object",
|
|
13
|
+
"minProperties": 1,
|
|
14
|
+
"maxProperties": 32,
|
|
15
|
+
"propertyNames": { "$ref": "#/definitions/IntegrationAlias" },
|
|
16
|
+
"additionalProperties": { "$ref": "#/definitions/DeclaredConnection" }
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"definitions": {
|
|
20
|
+
"IntegrationAlias": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"pattern": "^[a-z][a-z0-9_-]{0,62}$",
|
|
23
|
+
"description": "Tenant-wide logical alias of a connection (governance sourceId) or of a resource under it."
|
|
24
|
+
},
|
|
25
|
+
"IntegrationConnectionKind": { "type": "string", "enum": ["saas", "database"] },
|
|
26
|
+
"IntegrationOperation": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"enum": ["slack.channel.history", "slack.message.post", "warehouse.view.read"],
|
|
29
|
+
"description": "Closed operation set. Extending it is a contracts change."
|
|
30
|
+
},
|
|
31
|
+
"IntegrationIdentityMode": {
|
|
32
|
+
"type": "string",
|
|
33
|
+
"enum": ["app", "user"],
|
|
34
|
+
"description": "Whose authority an operation runs under. Fixed per operation; callers never choose it."
|
|
35
|
+
},
|
|
36
|
+
"IntegrationEnvironment": {
|
|
37
|
+
"type": "string",
|
|
38
|
+
"enum": ["development", "preview", "production"],
|
|
39
|
+
"description": "development is the CLI/local kit; preview and production are hosted."
|
|
40
|
+
},
|
|
41
|
+
"ResourceNames": {
|
|
42
|
+
"type": "array",
|
|
43
|
+
"minItems": 1,
|
|
44
|
+
"maxItems": 64,
|
|
45
|
+
"items": { "$ref": "#/definitions/IntegrationAlias" }
|
|
46
|
+
},
|
|
47
|
+
"ViewResource": {
|
|
48
|
+
"type": "object",
|
|
49
|
+
"additionalProperties": false,
|
|
50
|
+
"required": ["columns"],
|
|
51
|
+
"properties": {
|
|
52
|
+
"columns": {
|
|
53
|
+
"type": "array",
|
|
54
|
+
"minItems": 1,
|
|
55
|
+
"maxItems": 32,
|
|
56
|
+
"items": { "type": "string", "pattern": "^[A-Za-z_][A-Za-z0-9_]{0,127}$" }
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"UserModeChannelOperation": {
|
|
61
|
+
"type": "object",
|
|
62
|
+
"additionalProperties": false,
|
|
63
|
+
"required": ["identity", "resources"],
|
|
64
|
+
"properties": {
|
|
65
|
+
"identity": { "type": "string", "const": "user" },
|
|
66
|
+
"resources": { "$ref": "#/definitions/ResourceNames" }
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
"AppModeChannelOperation": {
|
|
70
|
+
"type": "object",
|
|
71
|
+
"additionalProperties": false,
|
|
72
|
+
"required": ["identity", "resources"],
|
|
73
|
+
"properties": {
|
|
74
|
+
"identity": { "type": "string", "const": "app" },
|
|
75
|
+
"resources": { "$ref": "#/definitions/ResourceNames" }
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
"AppModeViewOperation": {
|
|
79
|
+
"type": "object",
|
|
80
|
+
"additionalProperties": false,
|
|
81
|
+
"required": ["identity", "resources"],
|
|
82
|
+
"properties": {
|
|
83
|
+
"identity": { "type": "string", "const": "app" },
|
|
84
|
+
"resources": {
|
|
85
|
+
"type": "object",
|
|
86
|
+
"minProperties": 1,
|
|
87
|
+
"maxProperties": 64,
|
|
88
|
+
"propertyNames": { "$ref": "#/definitions/IntegrationAlias" },
|
|
89
|
+
"additionalProperties": { "$ref": "#/definitions/ViewResource" }
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
"DeclaredConnection": {
|
|
94
|
+
"type": "object",
|
|
95
|
+
"additionalProperties": false,
|
|
96
|
+
"required": ["kind", "operations"],
|
|
97
|
+
"properties": {
|
|
98
|
+
"kind": { "$ref": "#/definitions/IntegrationConnectionKind" },
|
|
99
|
+
"operations": {
|
|
100
|
+
"type": "object",
|
|
101
|
+
"additionalProperties": false,
|
|
102
|
+
"minProperties": 1,
|
|
103
|
+
"properties": {
|
|
104
|
+
"slack.channel.history": { "$ref": "#/definitions/UserModeChannelOperation" },
|
|
105
|
+
"slack.message.post": { "$ref": "#/definitions/AppModeChannelOperation" },
|
|
106
|
+
"warehouse.view.read": { "$ref": "#/definitions/AppModeViewOperation" }
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
"if": { "properties": { "kind": { "const": "saas" } } },
|
|
111
|
+
"then": { "properties": { "operations": { "not": { "required": ["warehouse.view.read"] } } } },
|
|
112
|
+
"else": {
|
|
113
|
+
"properties": {
|
|
114
|
+
"operations": {
|
|
115
|
+
"not": { "anyOf": [{ "required": ["slack.channel.history"] }, { "required": ["slack.message.post"] }] }
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
"examples": [
|
|
122
|
+
{
|
|
123
|
+
"schema": "harbour.app-integrations/2.0",
|
|
124
|
+
"connections": {
|
|
125
|
+
"company-slack": {
|
|
126
|
+
"kind": "saas",
|
|
127
|
+
"operations": {
|
|
128
|
+
"slack.channel.history": { "identity": "user", "resources": ["team-updates"] },
|
|
129
|
+
"slack.message.post": { "identity": "app", "resources": ["team-updates"] }
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
"sales-warehouse": {
|
|
133
|
+
"kind": "database",
|
|
134
|
+
"operations": {
|
|
135
|
+
"warehouse.view.read": {
|
|
136
|
+
"identity": "app",
|
|
137
|
+
"resources": { "weekly_sales": { "columns": ["week", "total"] } }
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
]
|
|
144
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://schemas.harbour.dev/data-grant-v2.schema.json",
|
|
4
|
+
"title": "DataGrantV2",
|
|
5
|
+
"description": "An app's integration grant for one connection, environment and identity mode (control table sk APP#<appId>#IGRANT#<environment>#<sourceId>#<mode>; grantId = base64url(JSON [\"v2\",appId,environment,sourceId,mode])). Additive to the legacy DataGrant (data-grant.schema.json), which is never consulted by v2 paths. Expiry is enforced from UTC time on every call and SDK calls never extend it. Status edges live in x-transitions; DENIED and REVOKED are terminal (a regrant after revocation is a new REQUESTED record once cleanup is DONE). Database grants add credentialRef/mintedPrincipal, SaaS grants add installation; both hold references only.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["schema", "appId", "sourceId", "kind", "environment", "identityMode", "status", "operations", "resources", "version", "provisioning"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"schema": { "type": "string", "const": "harbour.data-grant/2.0" },
|
|
11
|
+
"appId": { "type": "string", "minLength": 1, "maxLength": 255 },
|
|
12
|
+
"sourceId": { "$ref": "app-integrations-declaration.schema.json#/definitions/IntegrationAlias" },
|
|
13
|
+
"kind": { "$ref": "app-integrations-declaration.schema.json#/definitions/IntegrationConnectionKind" },
|
|
14
|
+
"environment": { "$ref": "app-integrations-declaration.schema.json#/definitions/IntegrationEnvironment" },
|
|
15
|
+
"identityMode": { "$ref": "app-integrations-declaration.schema.json#/definitions/IntegrationIdentityMode" },
|
|
16
|
+
"status": { "$ref": "#/definitions/DataGrantV2Status" },
|
|
17
|
+
"operations": {
|
|
18
|
+
"type": "array",
|
|
19
|
+
"minItems": 1,
|
|
20
|
+
"maxItems": 3,
|
|
21
|
+
"uniqueItems": true,
|
|
22
|
+
"items": { "$ref": "app-integrations-declaration.schema.json#/definitions/IntegrationOperation" },
|
|
23
|
+
"description": "Approved operations; each must match this grant's identityMode."
|
|
24
|
+
},
|
|
25
|
+
"resources": {
|
|
26
|
+
"type": "array",
|
|
27
|
+
"minItems": 1,
|
|
28
|
+
"maxItems": 64,
|
|
29
|
+
"uniqueItems": true,
|
|
30
|
+
"items": { "$ref": "app-integrations-declaration.schema.json#/definitions/IntegrationAlias" },
|
|
31
|
+
"description": "Approved logical resource names (channel aliases or view names). Never inherited by wildcard."
|
|
32
|
+
},
|
|
33
|
+
"expiresAt": { "type": "string", "format": "date-time", "description": "Absent only for explicitly approved ongoing production access." },
|
|
34
|
+
"approvedBy": { "type": "string", "minLength": 1, "maxLength": 255 },
|
|
35
|
+
"version": { "type": "integer", "minimum": 1, "description": "Optimistic-concurrency version; approvals compare it with the reviewed value." },
|
|
36
|
+
"pendingRequestId": { "type": "string", "minLength": 1, "maxLength": 128, "description": "The one open access request attached to this grant, if any." },
|
|
37
|
+
"provisioning": { "$ref": "#/definitions/DataGrantV2Provisioning" },
|
|
38
|
+
"credentialRef": {
|
|
39
|
+
"type": "string",
|
|
40
|
+
"pattern": "^secretref://[A-Za-z0-9][A-Za-z0-9/_.:-]*$",
|
|
41
|
+
"description": "Database grants: secretref://tenant/{t}/app/{a}/env/{env}/source/{s}."
|
|
42
|
+
},
|
|
43
|
+
"mintedPrincipal": { "type": "string", "minLength": 1, "maxLength": 255, "description": "Database grants: the deterministic per tenant/app/environment/source principal." },
|
|
44
|
+
"installation": { "type": "string", "pattern": "^[a-z][a-z0-9_-]{0,62}$", "description": "SaaS grants: the connection installation this environment maps to." }
|
|
45
|
+
},
|
|
46
|
+
"definitions": {
|
|
47
|
+
"DataGrantV2Status": {
|
|
48
|
+
"type": "string",
|
|
49
|
+
"enum": ["REQUESTED", "GRANTED", "DENIED", "REVOKED"],
|
|
50
|
+
"description": "Grant lifecycle state. Legal edges live in x-transitions; DENIED and REVOKED are terminal."
|
|
51
|
+
},
|
|
52
|
+
"DataGrantV2ProvisioningStatus": {
|
|
53
|
+
"type": "string",
|
|
54
|
+
"enum": ["NONE", "PENDING", "FAILED", "DONE"],
|
|
55
|
+
"description": "Provider-side work for the current approved scope (credential mint, cleanup, installation check)."
|
|
56
|
+
},
|
|
57
|
+
"DataGrantV2Provisioning": {
|
|
58
|
+
"type": "object",
|
|
59
|
+
"additionalProperties": false,
|
|
60
|
+
"required": ["status", "attempts"],
|
|
61
|
+
"properties": {
|
|
62
|
+
"status": { "$ref": "#/definitions/DataGrantV2ProvisioningStatus" },
|
|
63
|
+
"attempts": { "type": "integer", "minimum": 0, "maximum": 3, "description": "Failed attempts; three requires an admin retry." },
|
|
64
|
+
"lastError": { "type": "string", "maxLength": 500, "description": "Sanitized reason; never a provider error body." }
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"x-status-definition": "DataGrantV2Status",
|
|
69
|
+
"x-terminal": ["DENIED", "REVOKED"],
|
|
70
|
+
"x-transitions": [
|
|
71
|
+
{ "from": "REQUESTED", "to": "GRANTED", "trigger": "approval published conditionally after provisioning verified (or auto-approved within development preapproval)" },
|
|
72
|
+
{ "from": "REQUESTED", "to": "DENIED", "trigger": "admin denies the request" },
|
|
73
|
+
{ "from": "GRANTED", "to": "REVOKED", "trigger": "admin revokes; Isomorph access is cut first, provider cleanup follows" }
|
|
74
|
+
],
|
|
75
|
+
"examples": [
|
|
76
|
+
{
|
|
77
|
+
"schema": "harbour.data-grant/2.0",
|
|
78
|
+
"appId": "app-1f2e3d4c",
|
|
79
|
+
"sourceId": "company-slack",
|
|
80
|
+
"kind": "saas",
|
|
81
|
+
"environment": "development",
|
|
82
|
+
"identityMode": "user",
|
|
83
|
+
"status": "GRANTED",
|
|
84
|
+
"operations": ["slack.channel.history"],
|
|
85
|
+
"resources": ["team-updates"],
|
|
86
|
+
"expiresAt": "2026-09-10T08:00:00Z",
|
|
87
|
+
"approvedBy": "policy:development-preapproval",
|
|
88
|
+
"version": 2,
|
|
89
|
+
"provisioning": { "status": "DONE", "attempts": 0 },
|
|
90
|
+
"installation": "nonprod"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"schema": "harbour.data-grant/2.0",
|
|
94
|
+
"appId": "app-1f2e3d4c",
|
|
95
|
+
"sourceId": "sales-warehouse",
|
|
96
|
+
"kind": "database",
|
|
97
|
+
"environment": "preview",
|
|
98
|
+
"identityMode": "app",
|
|
99
|
+
"status": "REQUESTED",
|
|
100
|
+
"operations": ["warehouse.view.read"],
|
|
101
|
+
"resources": ["weekly_sales"],
|
|
102
|
+
"expiresAt": "2026-09-16T08:00:00Z",
|
|
103
|
+
"version": 1,
|
|
104
|
+
"pendingRequestId": "req_9a8b7c6d",
|
|
105
|
+
"provisioning": { "status": "PENDING", "attempts": 1, "lastError": "credential verification timed out" }
|
|
106
|
+
}
|
|
107
|
+
]
|
|
108
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://schemas.harbour.dev/integration-connect-result.schema.json",
|
|
4
|
+
"title": "IntegrationConnectResult",
|
|
5
|
+
"description": "The `data` member of the connect (POST) and disconnect (DELETE) responses on /_harbour/integrations/connect. connected: this user's consent link for the app/environment/connection is ACTIVE (accountLabel is a display label, never an ID or token); consent_required: the user must follow authorizationUrl (governance OAuth start with an opaque ticket); disconnected: only this user's consent for this app/environment was revoked. Connect is a user action and never requests IT app approval.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["status"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"status": { "type": "string", "enum": ["connected", "consent_required", "disconnected"] },
|
|
11
|
+
"accountLabel": { "type": "string", "minLength": 1, "maxLength": 255 },
|
|
12
|
+
"authorizationUrl": { "type": "string", "format": "uri", "pattern": "^https://", "maxLength": 2048 }
|
|
13
|
+
},
|
|
14
|
+
"oneOf": [
|
|
15
|
+
{ "properties": { "status": { "const": "connected" } }, "required": ["accountLabel"], "maxProperties": 2 },
|
|
16
|
+
{ "properties": { "status": { "const": "consent_required" } }, "required": ["authorizationUrl"], "maxProperties": 2 },
|
|
17
|
+
{ "properties": { "status": { "const": "disconnected" } }, "maxProperties": 1 }
|
|
18
|
+
],
|
|
19
|
+
"examples": [
|
|
20
|
+
{ "status": "connected", "accountLabel": "Acme Workspace (jane)" },
|
|
21
|
+
{ "status": "consent_required", "authorizationUrl": "https://governance.example/v1/integrations/oauth/slack/start?ticket=opaque" },
|
|
22
|
+
{ "status": "disconnected" }
|
|
23
|
+
]
|
|
24
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://schemas.harbour.dev/integration-error-details.schema.json",
|
|
4
|
+
"title": "IntegrationErrorDetails",
|
|
5
|
+
"description": "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.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["category", "message", "details"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"category": { "$ref": "#/definitions/IntegrationErrorCategory" },
|
|
11
|
+
"message": { "type": "string", "minLength": 1, "maxLength": 500 },
|
|
12
|
+
"details": {
|
|
13
|
+
"type": "object",
|
|
14
|
+
"additionalProperties": false,
|
|
15
|
+
"required": ["code"],
|
|
16
|
+
"properties": {
|
|
17
|
+
"code": { "$ref": "#/definitions/IntegrationErrorCode" },
|
|
18
|
+
"retryAfterSeconds": {
|
|
19
|
+
"type": "integer",
|
|
20
|
+
"minimum": 1,
|
|
21
|
+
"maximum": 86400,
|
|
22
|
+
"description": "Mirrors the Retry-After header on RATE_LIMITED responses."
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"definitions": {
|
|
28
|
+
"IntegrationErrorCategory": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"enum": ["AUTH_REQUIRED", "FORBIDDEN", "NOT_FOUND", "CONFLICT", "VALIDATION_FAILED", "RATE_LIMITED", "UNAVAILABLE"],
|
|
31
|
+
"description": "App-SDK HarbourErrorCategory values an integration route may emit; INTERNAL is reserved for the transport."
|
|
32
|
+
},
|
|
33
|
+
"IntegrationErrorCode": {
|
|
34
|
+
"type": "string",
|
|
35
|
+
"enum": [
|
|
36
|
+
"USER_CONNECTION_REQUIRED",
|
|
37
|
+
"USER_RECONNECT_REQUIRED",
|
|
38
|
+
"GRANT_NOT_READY",
|
|
39
|
+
"GRANT_EXPIRED",
|
|
40
|
+
"GRANT_REVOKED",
|
|
41
|
+
"CONNECTION_DISABLED",
|
|
42
|
+
"OPERATION_NOT_DECLARED",
|
|
43
|
+
"RESOURCE_NOT_APPROVED",
|
|
44
|
+
"IDENTITY_MODE_MISMATCH",
|
|
45
|
+
"SEND_OUTCOME_UNKNOWN",
|
|
46
|
+
"SEND_KEY_REUSED",
|
|
47
|
+
"PROVIDER_RATE_LIMITED",
|
|
48
|
+
"PROVIDER_UNAVAILABLE",
|
|
49
|
+
"REQUEST_TOO_LARGE",
|
|
50
|
+
"RESULT_TOO_LARGE",
|
|
51
|
+
"DEADLINE_EXCEEDED",
|
|
52
|
+
"APP_ACCESS_REQUIRED",
|
|
53
|
+
"GRANT_DENIED",
|
|
54
|
+
"TOKEN_REFRESH_PENDING",
|
|
55
|
+
"REQUEST_PENDING"
|
|
56
|
+
],
|
|
57
|
+
"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."
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"examples": [
|
|
61
|
+
{ "category": "FORBIDDEN", "message": "Connect your Slack account to read team-updates.", "details": { "code": "USER_CONNECTION_REQUIRED" } },
|
|
62
|
+
{ "category": "RATE_LIMITED", "message": "Slack is rate limiting this workspace.", "details": { "code": "PROVIDER_RATE_LIMITED", "retryAfterSeconds": 30 } }
|
|
63
|
+
]
|
|
64
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://schemas.harbour.dev/integration-execute-request.schema.json",
|
|
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; warehouse.view.read {columns 1..32, limit? 1..1000, default 100}. Transport limits (not expressible here): request <= 16 KiB, total deadline 10 s.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["connection", "operation", "resource", "input"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"connection": { "$ref": "app-integrations-declaration.schema.json#/definitions/IntegrationAlias" },
|
|
11
|
+
"operation": { "$ref": "app-integrations-declaration.schema.json#/definitions/IntegrationOperation" },
|
|
12
|
+
"resource": { "$ref": "app-integrations-declaration.schema.json#/definitions/IntegrationAlias" },
|
|
13
|
+
"input": {
|
|
14
|
+
"type": "object",
|
|
15
|
+
"additionalProperties": false,
|
|
16
|
+
"properties": {
|
|
17
|
+
"limit": { "type": "integer", "minimum": 1, "maximum": 1000 },
|
|
18
|
+
"text": { "type": "string", "minLength": 1, "maxLength": 4000 },
|
|
19
|
+
"columns": {
|
|
20
|
+
"type": "array",
|
|
21
|
+
"minItems": 1,
|
|
22
|
+
"maxItems": 32,
|
|
23
|
+
"items": { "type": "string", "pattern": "^[A-Za-z_][A-Za-z0-9_]{0,127}$" }
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"idempotencyKey": {
|
|
28
|
+
"type": "string",
|
|
29
|
+
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
|
|
30
|
+
"description": "UUID of one explicit Send action; required for slack.message.post only. The same key with the same payload and subject returns the recorded outcome for seven days."
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"allOf": [
|
|
34
|
+
{
|
|
35
|
+
"if": { "properties": { "operation": { "const": "slack.channel.history" } } },
|
|
36
|
+
"then": {
|
|
37
|
+
"not": { "required": ["idempotencyKey"] },
|
|
38
|
+
"properties": {
|
|
39
|
+
"input": { "properties": { "limit": { "maximum": 15 } }, "not": { "anyOf": [{ "required": ["text"] }, { "required": ["columns"] }] } }
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"if": { "properties": { "operation": { "const": "slack.message.post" } } },
|
|
45
|
+
"then": {
|
|
46
|
+
"required": ["idempotencyKey"],
|
|
47
|
+
"properties": {
|
|
48
|
+
"input": { "required": ["text"], "not": { "anyOf": [{ "required": ["limit"] }, { "required": ["columns"] }] } }
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"if": { "properties": { "operation": { "const": "warehouse.view.read" } } },
|
|
54
|
+
"then": {
|
|
55
|
+
"not": { "required": ["idempotencyKey"] },
|
|
56
|
+
"properties": {
|
|
57
|
+
"input": { "required": ["columns"], "not": { "required": ["text"] } }
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
],
|
|
62
|
+
"examples": [
|
|
63
|
+
{ "connection": "company-slack", "operation": "slack.channel.history", "resource": "team-updates", "input": { "limit": 15 } },
|
|
64
|
+
{
|
|
65
|
+
"connection": "company-slack",
|
|
66
|
+
"operation": "slack.message.post",
|
|
67
|
+
"resource": "team-updates",
|
|
68
|
+
"input": { "text": "Weekly update posted." },
|
|
69
|
+
"idempotencyKey": "6f1c2c1e-3a1c-4c3e-9f0e-2b6f7d8a9c10"
|
|
70
|
+
},
|
|
71
|
+
{ "connection": "sales-warehouse", "operation": "warehouse.view.read", "resource": "weekly_sales", "input": { "columns": ["week", "total"], "limit": 100 } }
|
|
72
|
+
]
|
|
73
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://schemas.harbour.dev/integration-execute-result.schema.json",
|
|
4
|
+
"title": "IntegrationExecuteResult",
|
|
5
|
+
"description": "The `data` member of a successful execute response `{data, requestId}`; the SDK unwraps `data`. Exactly one of the three operation results: slack.channel.history -> {messages, hasMore} (plain text/IDs/time only, never the provider payload); slack.message.post -> {messageId}; 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
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"properties": {
|
|
9
|
+
"messages": {
|
|
10
|
+
"type": "array",
|
|
11
|
+
"maxItems": 15,
|
|
12
|
+
"items": { "$ref": "#/definitions/ChannelMessage" }
|
|
13
|
+
},
|
|
14
|
+
"hasMore": { "type": "boolean" },
|
|
15
|
+
"messageId": { "type": "string", "minLength": 1, "maxLength": 128 },
|
|
16
|
+
"rows": {
|
|
17
|
+
"type": "array",
|
|
18
|
+
"maxItems": 1000,
|
|
19
|
+
"items": { "type": "object", "additionalProperties": {}, "description": "One row keyed by the requested column names." }
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"oneOf": [
|
|
23
|
+
{ "required": ["messages", "hasMore"], "maxProperties": 2 },
|
|
24
|
+
{ "required": ["messageId"], "maxProperties": 1 },
|
|
25
|
+
{ "required": ["rows"], "maxProperties": 1 }
|
|
26
|
+
],
|
|
27
|
+
"definitions": {
|
|
28
|
+
"ChannelMessage": {
|
|
29
|
+
"type": "object",
|
|
30
|
+
"additionalProperties": false,
|
|
31
|
+
"required": ["id", "text", "authorId", "createdAt"],
|
|
32
|
+
"properties": {
|
|
33
|
+
"id": { "type": "string", "minLength": 1, "maxLength": 128 },
|
|
34
|
+
"text": { "type": "string", "maxLength": 40000 },
|
|
35
|
+
"authorId": { "type": "string", "minLength": 1, "maxLength": 128 },
|
|
36
|
+
"createdAt": { "type": "string", "format": "date-time" }
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"examples": [
|
|
41
|
+
{
|
|
42
|
+
"messages": [{ "id": "1725840000.000100", "text": "Shipped the weekly report.", "authorId": "U0123456", "createdAt": "2026-09-09T08:00:00Z" }],
|
|
43
|
+
"hasMore": false
|
|
44
|
+
},
|
|
45
|
+
{ "messageId": "1725840100.000200" },
|
|
46
|
+
{ "rows": [{ "week": "2026-W36", "total": "1234.50" }] }
|
|
47
|
+
]
|
|
48
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://schemas.harbour.dev/kit-bundle-manifest.schema.json",
|
|
4
|
+
"title": "KitBundleManifest",
|
|
5
|
+
"description": "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).",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["schema", "kitVersion", "sdk", "images", "brief", "declarationSchema"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"schema": { "type": "string", "const": "harbour.kit-bundle/1.0" },
|
|
11
|
+
"kitVersion": { "type": "string", "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+(-[0-9A-Za-z.-]+)?$", "description": "The CLI package version that published this bundle." },
|
|
12
|
+
"sdk": {
|
|
13
|
+
"type": "object",
|
|
14
|
+
"additionalProperties": false,
|
|
15
|
+
"required": ["package", "tarballSha256"],
|
|
16
|
+
"properties": {
|
|
17
|
+
"package": { "type": "string", "const": "@harbour/app-sdk" },
|
|
18
|
+
"version": { "type": "string", "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+(-[0-9A-Za-z.-]+)?$", "description": "The SDK package version the tarball was packed from (informational; compatibility is decided by tarballSha256)." },
|
|
19
|
+
"tarballSha256": { "$ref": "#/definitions/HexSha256", "description": "SHA-256 of the SDK tarball baked into this bundle; the CodeBuild gate compares it with the pipeline's own tarball." },
|
|
20
|
+
"url": { "type": "string", "pattern": "^https://[^\\s{}]+$", "maxLength": 2048, "description": "Where the CLI downloads the SDK tarball from when it is not shipped inline; the bytes are verified against tarballSha256 before install. The publisher may name the location by content address (for example an OCI blob whose path ends in sha256:<tarballSha256>)." }
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"images": {
|
|
24
|
+
"type": "object",
|
|
25
|
+
"additionalProperties": false,
|
|
26
|
+
"required": ["appGateway", "sessionFixture"],
|
|
27
|
+
"properties": {
|
|
28
|
+
"appGateway": { "$ref": "#/definitions/ImageDigestReference" },
|
|
29
|
+
"sessionFixture": { "$ref": "#/definitions/ImageDigestReference" }
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"brief": {
|
|
33
|
+
"type": "object",
|
|
34
|
+
"additionalProperties": false,
|
|
35
|
+
"required": ["fingerprint"],
|
|
36
|
+
"properties": {
|
|
37
|
+
"fingerprint": {
|
|
38
|
+
"type": "string",
|
|
39
|
+
"pattern": "^([0-9a-f]{64}|unknown)$",
|
|
40
|
+
"description": "SHA-256 of the agent-brief tar, or the literal unknown when the bundle carried no brief."
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"declarationSchema": { "type": "string", "const": "harbour.app-integrations/2.0" }
|
|
45
|
+
},
|
|
46
|
+
"definitions": {
|
|
47
|
+
"HexSha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
|
|
48
|
+
"ImageDigestReference": {
|
|
49
|
+
"type": "string",
|
|
50
|
+
"pattern": "^[a-z0-9][a-z0-9._/-]*(:[0-9]+)?(/[a-z0-9._-]+)*@sha256:[0-9a-f]{64}$",
|
|
51
|
+
"maxLength": 512,
|
|
52
|
+
"description": "Digest-pinned image reference <repository>@sha256:<hex>; tags are never accepted."
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"examples": [
|
|
56
|
+
{
|
|
57
|
+
"schema": "harbour.kit-bundle/1.0",
|
|
58
|
+
"kitVersion": "0.2.0",
|
|
59
|
+
"sdk": {
|
|
60
|
+
"package": "@harbour/app-sdk",
|
|
61
|
+
"version": "1.0.0",
|
|
62
|
+
"tarballSha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
|
|
63
|
+
"url": "https://public.ecr.aws/v2/y6t4p3i8/harbour-kit-bundle/blobs/sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
|
|
64
|
+
},
|
|
65
|
+
"images": {
|
|
66
|
+
"appGateway": "123456789012.dkr.ecr.us-east-1.amazonaws.com/harbour/app-gateway@sha256:2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae",
|
|
67
|
+
"sessionFixture": "123456789012.dkr.ecr.us-east-1.amazonaws.com/harbour/session-fixture@sha256:fcde2b2edba56bf408601fb721fe9b5c338d10ee429ea04fae5511b68fbf8fb9"
|
|
68
|
+
},
|
|
69
|
+
"brief": { "fingerprint": "unknown" },
|
|
70
|
+
"declarationSchema": "harbour.app-integrations/2.0"
|
|
71
|
+
}
|
|
72
|
+
]
|
|
73
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://schemas.harbour.dev/kit-lock.schema.json",
|
|
4
|
+
"title": "KitLock",
|
|
5
|
+
"description": "Committed `.harbour/kit.lock.json`: which app and tenant a kit-built app belongs to and the exact bundle it was developed against. The data-plane vendorAppSDK gate reads bundle.sdk.tarballSha256 and fails with kit_bundle_incompatible (naming both digests) when it differs from the baked tarball; apps without a kit.lock keep the existing path.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["schema", "appId", "tenantId", "bundle", "createdAt", "updatedAt"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"schema": { "type": "string", "const": "harbour.kit-lock/1.0" },
|
|
11
|
+
"appId": { "type": "string", "pattern": "^app-[0-9a-f]{8,}$" },
|
|
12
|
+
"tenantId": { "type": "string", "minLength": 1, "maxLength": 128 },
|
|
13
|
+
"bundle": { "$ref": "kit-bundle-manifest.schema.json" },
|
|
14
|
+
"createdAt": { "type": "string", "format": "date-time" },
|
|
15
|
+
"updatedAt": { "type": "string", "format": "date-time" }
|
|
16
|
+
},
|
|
17
|
+
"examples": [
|
|
18
|
+
{
|
|
19
|
+
"schema": "harbour.kit-lock/1.0",
|
|
20
|
+
"appId": "app-1f2e3d4c",
|
|
21
|
+
"tenantId": "fourier",
|
|
22
|
+
"bundle": {
|
|
23
|
+
"schema": "harbour.kit-bundle/1.0",
|
|
24
|
+
"kitVersion": "0.2.0",
|
|
25
|
+
"sdk": { "package": "@harbour/app-sdk", "tarballSha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08" },
|
|
26
|
+
"images": {
|
|
27
|
+
"appGateway": "123456789012.dkr.ecr.us-east-1.amazonaws.com/harbour/app-gateway@sha256:2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae",
|
|
28
|
+
"sessionFixture": "123456789012.dkr.ecr.us-east-1.amazonaws.com/harbour/session-fixture@sha256:fcde2b2edba56bf408601fb721fe9b5c338d10ee429ea04fae5511b68fbf8fb9"
|
|
29
|
+
},
|
|
30
|
+
"brief": { "fingerprint": "unknown" },
|
|
31
|
+
"declarationSchema": "harbour.app-integrations/2.0"
|
|
32
|
+
},
|
|
33
|
+
"createdAt": "2026-09-09T08:00:00Z",
|
|
34
|
+
"updatedAt": "2026-09-09T08:00:00Z"
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
}
|