@fourier-labs/harbour-contracts 0.46.0 → 0.50.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 +32 -6
- package/contract-release.json +30 -30
- package/dist/gen-ts/ai-policy-snapshot.d.ts +19 -0
- package/dist/gen-ts/ai-runtime-contract.d.ts +1 -1
- package/dist/gen-ts/ai-trace-event.d.ts +4 -1
- package/dist/gen-ts/app-integrations-declaration.d.ts +9 -2
- 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 +60 -16
- package/dist/gen-ts/integration-error-details.d.ts +2 -2
- package/dist/gen-ts/integration-execute-request.d.ts +44 -4
- package/dist/gen-ts/integration-execute-result.d.ts +21 -1
- package/dist/gen-ts/saas-connection.d.ts +80 -29
- package/dist/gen-ts/transform-report.d.ts +2 -0
- package/dist/gen-ts/well-defined-app-manifest.d.ts +31 -0
- package/dist/resolver/index.d.ts +7 -1
- package/gen-ts/ai-policy-snapshot.ts +19 -0
- package/gen-ts/ai-runtime-contract.ts +1 -1
- package/gen-ts/ai-trace-event.ts +4 -1
- package/gen-ts/app-integrations-declaration.ts +9 -2
- package/gen-ts/contract-release.ts +6 -6
- package/gen-ts/data-grant-v2.ts +67 -16
- package/gen-ts/integration-error-details.ts +10 -2
- package/gen-ts/integration-execute-request.ts +50 -4
- package/gen-ts/integration-execute-result.ts +21 -1
- package/gen-ts/saas-connection.ts +87 -29
- package/gen-ts/transform-report.ts +2 -0
- package/gen-ts/well-defined-app-manifest.ts +31 -0
- package/package.json +1 -1
- package/schema/ai-policy-snapshot.schema.json +13 -1
- package/schema/ai-runtime-contract.schema.json +1 -1
- package/schema/ai-trace-event.schema.json +7 -1
- package/schema/app-integrations-declaration.schema.json +22 -6
- package/schema/data-grant-v2.schema.json +102 -23
- package/schema/integration-error-details.schema.json +10 -2
- package/schema/integration-execute-request.schema.json +53 -7
- package/schema/integration-execute-result.schema.json +36 -1
- package/schema/saas-connection.schema.json +97 -41
- package/schema/transform-report.schema.json +2 -1
- package/schema/well-defined-app-manifest.schema.json +61 -0
|
@@ -1,59 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slack carries an installation and a channel map; Gmail is user-consent only and read-only.
|
|
3
|
+
*/
|
|
4
|
+
export type SaasProvider = 'slack' | 'gmail';
|
|
1
5
|
/**
|
|
2
6
|
* DISABLED stops every app call immediately (details.code CONNECTION_DISABLED).
|
|
3
7
|
*/
|
|
4
8
|
export type SaasConnectionStatus = 'ACTIVE' | 'DISABLED';
|
|
9
|
+
export type SaasInstallationStatus = 'installed' | 'reconnect_required' | 'revoked';
|
|
10
|
+
/**
|
|
11
|
+
*/
|
|
5
12
|
export type InstallationName = string;
|
|
6
13
|
/**
|
|
7
|
-
* Tenant-wide SaaS connection record (control table sk SAASCONN#<sourceId>, same tenant partition as database sources; alias uniqueness is enforced across both).
|
|
14
|
+
* Tenant-wide SaaS connection record (control table sk SAASCONN#<sourceId>, same tenant partition as database sources; alias uniqueness is enforced across both). One connection per tenant and provider: a single bot installation of the platform-owned provider app and a single logical channel map shared by every app environment (grants, requests and user consent links stay per environment). A Gmail connection has neither installation nor channels: user consent is the only step and its one resource is the fixed `inbox` alias. Contains references, never secrets: installation tokens live under their own token records. The pre-T25 per-environment shape (`clientId`, `clientSecretRef`, `environments`, `installations`) is deprecated: the control plane normalises such a stored record on read and never writes it.
|
|
8
15
|
*/
|
|
9
16
|
export interface SaasConnection {
|
|
17
|
+
schema: 'harbour.saas-connection/1.0';
|
|
18
|
+
tenantId: string;
|
|
10
19
|
/**
|
|
11
20
|
* Tenant-wide logical alias of a connection (governance sourceId) or of a resource under it.
|
|
12
21
|
*/
|
|
13
22
|
sourceId: string;
|
|
14
23
|
kind: 'saas';
|
|
15
|
-
provider:
|
|
24
|
+
provider: SaasProvider;
|
|
16
25
|
displayName: string;
|
|
17
26
|
status: SaasConnectionStatus;
|
|
18
27
|
/**
|
|
19
28
|
* Optimistic-concurrency version; every write is conditional on it.
|
|
20
29
|
*/
|
|
21
30
|
version: number;
|
|
22
|
-
|
|
31
|
+
installation?: SaasInstallation;
|
|
23
32
|
/**
|
|
24
|
-
*
|
|
33
|
+
* Logical channel alias -> provider channel ID, shared by every environment. Always empty for Gmail.
|
|
25
34
|
*/
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
35
|
+
channels: {
|
|
36
|
+
/**
|
|
37
|
+
* Provider channel ID; only public, non-archived channels are valid.
|
|
38
|
+
*/
|
|
39
|
+
[k: string]: string;
|
|
31
40
|
};
|
|
32
41
|
/**
|
|
33
|
-
* Operations IT allows apps to request on this connection.
|
|
42
|
+
* Operations IT allows apps to request on this connection: the provider's own SaaS operations (Slack: slack.channel.history, slack.message.post; Gmail: gmail.thread.list, gmail.message.read, gmail.message.send). Never warehouse.view.read.
|
|
34
43
|
*
|
|
35
44
|
* @minItems 1
|
|
36
|
-
* @maxItems
|
|
45
|
+
* @maxItems 5
|
|
37
46
|
*/
|
|
38
|
-
operations: ('slack.channel.history' | 'slack.message.post')[];
|
|
47
|
+
operations: ('slack.channel.history' | 'slack.message.post' | 'gmail.thread.list' | 'gmail.message.read' | 'gmail.message.send' | 'warehouse.view.read')[];
|
|
39
48
|
developmentPreapproval: DevelopmentPreapproval;
|
|
40
49
|
/**
|
|
41
|
-
*
|
|
50
|
+
* Lower-cased builder emails excluded from development preapproval.
|
|
51
|
+
*
|
|
52
|
+
* @maxItems 256
|
|
53
|
+
*/
|
|
54
|
+
blockedDevelopmentBuilders?: string[];
|
|
55
|
+
createdAt: string;
|
|
56
|
+
updatedAt: string;
|
|
57
|
+
/**
|
|
58
|
+
* @deprecated
|
|
59
|
+
* Deprecated (pre-T25 tenant-owned provider client). Dropped on read; never written.
|
|
60
|
+
*/
|
|
61
|
+
clientId?: string;
|
|
62
|
+
/**
|
|
63
|
+
* @deprecated
|
|
64
|
+
* Deprecated (pre-T25 tenant-owned provider client secret reference). Dropped on read; never written.
|
|
65
|
+
*/
|
|
66
|
+
clientSecretRef?: string;
|
|
67
|
+
/**
|
|
68
|
+
* @deprecated
|
|
69
|
+
* Deprecated pre-T25 per-environment mapping. On read the development entry's channels become `channels`; never written.
|
|
70
|
+
*/
|
|
71
|
+
environments?: {
|
|
72
|
+
development?: SaasEnvironmentMapping;
|
|
73
|
+
preview?: SaasEnvironmentMapping;
|
|
74
|
+
production?: SaasEnvironmentMapping;
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* @deprecated
|
|
78
|
+
* Deprecated pre-T25 named installations. On read the `nonprod` entry (or the development mapping's) becomes `installation`; never written.
|
|
42
79
|
*/
|
|
43
80
|
installations?: {
|
|
44
|
-
[k: string]:
|
|
81
|
+
[k: string]: LegacySaasInstallation;
|
|
45
82
|
};
|
|
46
|
-
createdAt?: string;
|
|
47
|
-
updatedAt?: string;
|
|
48
83
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
84
|
+
/**
|
|
85
|
+
* The one bot installation of the platform-owned provider app in this tenant's workspace.
|
|
86
|
+
*/
|
|
87
|
+
export interface SaasInstallation {
|
|
88
|
+
tokenRecordId: string;
|
|
89
|
+
workspaceId: string;
|
|
90
|
+
/**
|
|
91
|
+
* Empty only for a legacy record normalised without one.
|
|
92
|
+
*/
|
|
93
|
+
botUserId: string;
|
|
94
|
+
status: SaasInstallationStatus;
|
|
95
|
+
installedAt: string;
|
|
57
96
|
}
|
|
58
97
|
export interface DevelopmentPreapproval {
|
|
59
98
|
/**
|
|
@@ -65,9 +104,21 @@ export interface DevelopmentPreapproval {
|
|
|
65
104
|
*/
|
|
66
105
|
hours: number;
|
|
67
106
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
107
|
+
/**
|
|
108
|
+
* @deprecated
|
|
109
|
+
*/
|
|
110
|
+
export interface SaasEnvironmentMapping {
|
|
111
|
+
installation: InstallationName;
|
|
112
|
+
channels: {
|
|
113
|
+
[k: string]: string;
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* @deprecated
|
|
118
|
+
*/
|
|
119
|
+
export interface LegacySaasInstallation {
|
|
120
|
+
tokenRecordId?: string;
|
|
121
|
+
workspaceId?: string;
|
|
122
|
+
botUserId?: string;
|
|
123
|
+
status?: 'ACTIVE' | 'RECONNECT_REQUIRED' | 'REVOKED';
|
|
73
124
|
}
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export type TransformReport = {
|
|
5
5
|
/**
|
|
6
|
+
* Optional self-report of the playbooks the agent says it applied. A claim about its own behaviour is not evidence, so nothing validates it; a deployable report may omit it, and one that still carries it stays valid.
|
|
7
|
+
*
|
|
6
8
|
* @minItems 1
|
|
7
9
|
*/
|
|
8
10
|
applied_playbooks?: string[];
|
|
@@ -15,6 +15,7 @@ export type Workload = {
|
|
|
15
15
|
*/
|
|
16
16
|
export type Command = string[];
|
|
17
17
|
export type ResourceProfile = 'standard' | 'memory-1g' | 'memory-2g';
|
|
18
|
+
export type Capability = 'data' | 'files' | 'actions' | 'realtime' | 'telemetry';
|
|
18
19
|
/**
|
|
19
20
|
* Versioned provisioning input emitted by Harbour Transform. It describes runtime wiring and workload lifecycle only; PostgreSQL grants and RLS remain the database authorization authority.
|
|
20
21
|
*/
|
|
@@ -33,6 +34,17 @@ export interface WellDefinedAppManifest {
|
|
|
33
34
|
* @maxItems 128
|
|
34
35
|
*/
|
|
35
36
|
bindings: WorkloadBinding[];
|
|
37
|
+
/**
|
|
38
|
+
* Optional language runtime the application container is built on, spelled exactly as transform-report.application.runtime does (nodejs22, python3.11). Optional so a manifest written before the producer emits it still validates.
|
|
39
|
+
*/
|
|
40
|
+
runtime?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Optional declaration of the Harbour browser capabilities the application uses. Same closed set as the app-capabilities document; this is the declaration, not a source scan.
|
|
43
|
+
*
|
|
44
|
+
* @maxItems 5
|
|
45
|
+
*/
|
|
46
|
+
capabilities?: Capability[];
|
|
47
|
+
routeAuth?: RouteAuth;
|
|
36
48
|
}
|
|
37
49
|
export interface QueueConsumer {
|
|
38
50
|
logicalName: string;
|
|
@@ -64,3 +76,22 @@ export interface WorkloadBinding {
|
|
|
64
76
|
*/
|
|
65
77
|
reference?: string;
|
|
66
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Optional method/path authentication policy. Omitted means every route requires Harbour SSO, the same default the policy states explicitly.
|
|
81
|
+
*/
|
|
82
|
+
export interface RouteAuth {
|
|
83
|
+
default: 'sso_required';
|
|
84
|
+
/**
|
|
85
|
+
* @maxItems 64
|
|
86
|
+
*/
|
|
87
|
+
public: PublicRoute[];
|
|
88
|
+
}
|
|
89
|
+
export interface PublicRoute {
|
|
90
|
+
path: string;
|
|
91
|
+
/**
|
|
92
|
+
* @minItems 1
|
|
93
|
+
* @maxItems 7
|
|
94
|
+
*/
|
|
95
|
+
methods: ('GET' | 'HEAD' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS')[];
|
|
96
|
+
mode: 'application_authorized';
|
|
97
|
+
}
|
package/dist/resolver/index.d.ts
CHANGED
|
@@ -136,7 +136,13 @@ export declare function canonicalJson(value: unknown): string;
|
|
|
136
136
|
* to compile until someone states its merge rules. A `Pick<>` here previously
|
|
137
137
|
* let a seventh family parse, store, and enter the snapshot digest while being
|
|
138
138
|
* silently dropped from the effective policy. */
|
|
139
|
-
|
|
139
|
+
/** `assignments` sits here with `classificationMinimum` for the same reason:
|
|
140
|
+
* both are scope-LEVEL statements resolution consumes directly from the
|
|
141
|
+
* application snapshot (the classification raises the floor; the assignments
|
|
142
|
+
* select among the merged families by name), not per-entry families the
|
|
143
|
+
* tenant merge narrows. Excluding them keeps `SCOPE_FAMILY_MERGES` a registry
|
|
144
|
+
* of genuine family merges. */
|
|
145
|
+
type AiPolicySnapshotIdentity = "schema" | "scope" | "revision" | "publishedAt" | "contentDigest" | "baselineDigest" | "classificationMinimum" | "assignments";
|
|
140
146
|
/** The resource families a scope snapshot contributes to resolution. */
|
|
141
147
|
export type AiScopePolicySet = Omit<AiPolicySnapshot, AiPolicySnapshotIdentity>;
|
|
142
148
|
/** The merged control set every later layer narrowed but could not weaken. */
|
|
@@ -86,6 +86,7 @@ export interface AiPolicySnapshot {
|
|
|
86
86
|
*/
|
|
87
87
|
limits: AiPolicyLimit[];
|
|
88
88
|
retention: AiPolicyRetention;
|
|
89
|
+
assignments?: AiPolicyAssignments;
|
|
89
90
|
}
|
|
90
91
|
export interface AiPolicyClassificationMinimum {
|
|
91
92
|
classification: 'Green' | 'Yellow' | 'Orange' | 'Red';
|
|
@@ -204,3 +205,21 @@ export interface AiPolicyRetention {
|
|
|
204
205
|
*/
|
|
205
206
|
revealRoles: AiPolicyIdentifier[];
|
|
206
207
|
}
|
|
208
|
+
/**
|
|
209
|
+
* Application-scope only: the tenant resources this application/environment snapshot pins BY NAME (D-8/D-9). A scope without assignments keeps the default rule (the whole merge-narrowed set is the assignment). Every name must exist in the same snapshot's own family; empty route and budget selections are legal (tenant-scoped limits bind regardless).
|
|
210
|
+
*/
|
|
211
|
+
export interface AiPolicyAssignments {
|
|
212
|
+
/**
|
|
213
|
+
* @maxItems 256
|
|
214
|
+
*/
|
|
215
|
+
routes: AiPolicyIdentifier[];
|
|
216
|
+
/**
|
|
217
|
+
* @minItems 1
|
|
218
|
+
* @maxItems 256
|
|
219
|
+
*/
|
|
220
|
+
policies: AiPolicyIdentifier[];
|
|
221
|
+
/**
|
|
222
|
+
* @maxItems 128
|
|
223
|
+
*/
|
|
224
|
+
budgets: AiPolicyIdentifier[];
|
|
225
|
+
}
|
|
@@ -53,7 +53,7 @@ export interface AiRuntimeContract {
|
|
|
53
53
|
export interface AiConfigurationMappingDescriptor {
|
|
54
54
|
mappingId: string;
|
|
55
55
|
version: string;
|
|
56
|
-
protocol: 'openai-chat';
|
|
56
|
+
protocol: 'openai-chat' | 'openai-images';
|
|
57
57
|
providerId: string;
|
|
58
58
|
apiKeyEnvironmentName: string;
|
|
59
59
|
baseUrlEnvironmentName: string;
|
package/gen-ts/ai-trace-event.ts
CHANGED
|
@@ -20,7 +20,10 @@ export type AiTraceEvent = {
|
|
|
20
20
|
provider?: 'openai' | 'anthropic' | 'gemini' | 'openrouter' | 'deepseek' | 'elevenlabs' | 'deepgram' | 'higgsfield';
|
|
21
21
|
providerModel?: string;
|
|
22
22
|
outcome: 'succeeded' | 'blocked' | 'failed' | 'cancelled';
|
|
23
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Trace-level provider-reported usage, or null when no attempt reported any (the reservation stays committed rather than reconciled against invented zero usage).
|
|
25
|
+
*/
|
|
26
|
+
usage?: AiTraceUsage | null;
|
|
24
27
|
priceRevision?: AiTraceIdentifier;
|
|
25
28
|
costMicroUsd: number;
|
|
26
29
|
latencyMs: number;
|
|
@@ -11,9 +11,13 @@ export type IntegrationAlias = string;
|
|
|
11
11
|
* @maxItems 64
|
|
12
12
|
*/
|
|
13
13
|
export type ResourceNames = IntegrationAlias[];
|
|
14
|
+
/**
|
|
15
|
+
* A plain column identifier of a warehouse view.
|
|
16
|
+
*/
|
|
17
|
+
export type ColumnName = string;
|
|
14
18
|
|
|
15
19
|
/**
|
|
16
|
-
* 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 (
|
|
20
|
+
* Committed `.harbour/integrations.json`: the app's declared use of tenant-wide integration connections. Keys are logical connection aliases (the governance sourceId); resources are logical names (Slack channel alias, warehouse view name), never IDs, tokens or URLs. The operation set is closed (five operations) and each operation carries a fixed identity mode: slack.channel.history=user, slack.message.post=app, gmail.thread.list=user, gmail.message.read=user, warehouse.view.read=app. A `saas` connection may declare only the Slack and Gmail operations and a `database` connection only warehouse.view.read. Gmail is read-only and user-mode only; its single resource is the fixed `inbox` alias. This file also owns the shared IntegrationOperation / IntegrationIdentityMode / IntegrationEnvironment / IntegrationConnectionKind enums referenced by the other integration schemas.
|
|
17
21
|
*/
|
|
18
22
|
export interface AppIntegrationsDeclaration {
|
|
19
23
|
schema: 'harbour.app-integrations/2.0';
|
|
@@ -26,6 +30,9 @@ export interface DeclaredConnection {
|
|
|
26
30
|
operations: {
|
|
27
31
|
'slack.channel.history'?: UserModeChannelOperation;
|
|
28
32
|
'slack.message.post'?: AppModeChannelOperation;
|
|
33
|
+
'gmail.thread.list'?: UserModeChannelOperation;
|
|
34
|
+
'gmail.message.read'?: UserModeChannelOperation;
|
|
35
|
+
'gmail.message.send'?: UserModeChannelOperation;
|
|
29
36
|
'warehouse.view.read'?: AppModeViewOperation;
|
|
30
37
|
};
|
|
31
38
|
}
|
|
@@ -48,5 +55,5 @@ export interface ViewResource {
|
|
|
48
55
|
* @minItems 1
|
|
49
56
|
* @maxItems 32
|
|
50
57
|
*/
|
|
51
|
-
columns:
|
|
58
|
+
columns: ColumnName[];
|
|
52
59
|
}
|
|
@@ -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.50.0.0" as const;
|
|
5
|
+
export const ContractPublishVersion = "0.50.0" as const;
|
|
6
|
+
export const ContractPublishTag = "v0.50.0" as const;
|
|
7
|
+
export const ContractSchemasDigest = "sha256:c77dcbf9daad9c66141e9cb8215760a098afd1bf3e665e247f6a4df8db2bcd4f" 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:545d8b23ab712e75285ea962851bcf8f08f5baeb018bea6f7ba4e1ca2fe80ed0" as const;
|
|
10
|
+
export const ContractGeneratedTypeScriptDigest = "sha256:1e47c3956a3468531690693ff50ff7947a798af01e01d25659401f727cb934c4" as const;
|
package/gen-ts/data-grant-v2.ts
CHANGED
|
@@ -9,46 +9,58 @@ export type DataGrantV2Status = 'REQUESTED' | 'GRANTED' | 'DENIED' | 'REVOKED';
|
|
|
9
9
|
* Provider-side work for the current approved scope (credential mint, cleanup, installation check).
|
|
10
10
|
*/
|
|
11
11
|
export type DataGrantV2ProvisioningStatus = 'NONE' | 'PENDING' | 'FAILED' | 'DONE';
|
|
12
|
+
export type DataGrantV2CleanupStatus = 'PENDING' | 'FAILED' | 'DONE';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
|
-
* 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
|
|
15
|
+
* 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. A fresh REQUESTED record carries empty `operations`/`resources` until the attached request is approved; approved resources are structured `{name, columns?}` entries (a Slack channel alias, the fixed Gmail `inbox` alias, or a warehouse view alias with its approved columns). Expiry is enforced from UTC time on every call and SDK calls never extend it; `expiredAt` is stamped by the recovery sweep afterwards. 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; both hold references only. `identityMode` and `installation` are the deprecated 0.45.0 spellings: the record carries `mode`, and a SaaS grant runs on the connection's single installation.
|
|
15
16
|
*/
|
|
16
17
|
export interface DataGrantV2 {
|
|
17
18
|
schema: 'harbour.data-grant/2.0';
|
|
19
|
+
tenantId: string;
|
|
18
20
|
appId: string;
|
|
19
|
-
/**
|
|
20
|
-
* Tenant-wide logical alias of a connection (governance sourceId) or of a resource under it.
|
|
21
|
-
*/
|
|
22
|
-
sourceId: string;
|
|
23
|
-
kind: 'saas' | 'database';
|
|
24
21
|
/**
|
|
25
22
|
* development is the CLI/local kit; preview and production are hosted.
|
|
26
23
|
*/
|
|
27
24
|
environment: 'development' | 'preview' | 'production';
|
|
25
|
+
/**
|
|
26
|
+
* Tenant-wide logical alias of a connection (governance sourceId) or of a resource under it.
|
|
27
|
+
*/
|
|
28
|
+
sourceId: string;
|
|
28
29
|
/**
|
|
29
30
|
* Whose authority an operation runs under. Fixed per operation; callers never choose it.
|
|
30
31
|
*/
|
|
31
|
-
|
|
32
|
+
mode: 'app' | 'user';
|
|
33
|
+
kind: 'saas' | 'database';
|
|
32
34
|
status: DataGrantV2Status;
|
|
33
35
|
/**
|
|
34
|
-
* Approved operations; each must match this grant's
|
|
36
|
+
* Approved operations; each must match this grant's mode. Empty until the first approval.
|
|
35
37
|
*
|
|
36
|
-
* @
|
|
37
|
-
* @maxItems 3
|
|
38
|
+
* @maxItems 5
|
|
38
39
|
*/
|
|
39
|
-
operations: (
|
|
40
|
+
operations: (
|
|
41
|
+
| 'slack.channel.history'
|
|
42
|
+
| 'slack.message.post'
|
|
43
|
+
| 'gmail.thread.list'
|
|
44
|
+
| 'gmail.message.read'
|
|
45
|
+
| 'gmail.message.send'
|
|
46
|
+
| 'warehouse.view.read'
|
|
47
|
+
)[];
|
|
40
48
|
/**
|
|
41
|
-
* Approved logical
|
|
49
|
+
* Approved logical resources. Never inherited by wildcard. Empty until the first approval.
|
|
42
50
|
*
|
|
43
|
-
* @minItems 1
|
|
44
51
|
* @maxItems 64
|
|
45
52
|
*/
|
|
46
|
-
resources:
|
|
53
|
+
resources: IntegrationResource[];
|
|
47
54
|
/**
|
|
48
55
|
* Absent only for explicitly approved ongoing production access.
|
|
49
56
|
*/
|
|
50
57
|
expiresAt?: string;
|
|
58
|
+
/**
|
|
59
|
+
* Set by the recovery sweep once expiry has passed; enforcement itself is the UTC check on every call.
|
|
60
|
+
*/
|
|
61
|
+
expiredAt?: string;
|
|
51
62
|
approvedBy?: string;
|
|
63
|
+
approvedAt?: string;
|
|
52
64
|
/**
|
|
53
65
|
* Optimistic-concurrency version; approvals compare it with the reviewed value.
|
|
54
66
|
*/
|
|
@@ -59,18 +71,42 @@ export interface DataGrantV2 {
|
|
|
59
71
|
pendingRequestId?: string;
|
|
60
72
|
provisioning: DataGrantV2Provisioning;
|
|
61
73
|
/**
|
|
62
|
-
* Database grants: secretref://tenant/{t}/app/{a}/env/{env}/source/{s}.
|
|
74
|
+
* Database grants: secretref://tenant/{t}/app/{a}/env/{env}/source/{s}. Set only by a successful publish and removed by cleanup.
|
|
63
75
|
*/
|
|
64
76
|
credentialRef?: string;
|
|
65
77
|
/**
|
|
66
78
|
* Database grants: the deterministic per tenant/app/environment/source principal.
|
|
67
79
|
*/
|
|
68
80
|
mintedPrincipal?: string;
|
|
81
|
+
createdAt: string;
|
|
82
|
+
updatedAt: string;
|
|
69
83
|
/**
|
|
70
|
-
*
|
|
84
|
+
* @deprecated
|
|
85
|
+
* Whose authority an operation runs under. Fixed per operation; callers never choose it.
|
|
86
|
+
*/
|
|
87
|
+
identityMode?: 'app' | 'user';
|
|
88
|
+
/**
|
|
89
|
+
* @deprecated
|
|
90
|
+
* Deprecated: a SaaS grant runs on the connection's single installation; the control plane never writes it.
|
|
71
91
|
*/
|
|
72
92
|
installation?: string;
|
|
73
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* One approved logical resource: a channel alias, the fixed Gmail `inbox` alias, or a warehouse view alias with the approved columns.
|
|
96
|
+
*/
|
|
97
|
+
export interface IntegrationResource {
|
|
98
|
+
/**
|
|
99
|
+
* Tenant-wide logical alias of a connection (governance sourceId) or of a resource under it.
|
|
100
|
+
*/
|
|
101
|
+
name: string;
|
|
102
|
+
/**
|
|
103
|
+
* warehouse.view.read only: the approved columns of the view.
|
|
104
|
+
*
|
|
105
|
+
* @minItems 1
|
|
106
|
+
* @maxItems 32
|
|
107
|
+
*/
|
|
108
|
+
columns?: string[];
|
|
109
|
+
}
|
|
74
110
|
export interface DataGrantV2Provisioning {
|
|
75
111
|
status: DataGrantV2ProvisioningStatus;
|
|
76
112
|
/**
|
|
@@ -81,6 +117,21 @@ export interface DataGrantV2Provisioning {
|
|
|
81
117
|
* Sanitized reason; never a provider error body.
|
|
82
118
|
*/
|
|
83
119
|
lastError?: string;
|
|
120
|
+
/**
|
|
121
|
+
* Conditional worker lease serialising database mutations (120 s).
|
|
122
|
+
*/
|
|
123
|
+
lease?: {
|
|
124
|
+
until: string;
|
|
125
|
+
owner: string;
|
|
126
|
+
};
|
|
127
|
+
/**
|
|
128
|
+
* Provider-side cleanup after revocation or expiry; a FAILED cleanup waits for an admin retry-cleanup.
|
|
129
|
+
*/
|
|
130
|
+
cleanup?: {
|
|
131
|
+
status: DataGrantV2CleanupStatus;
|
|
132
|
+
attempts: number;
|
|
133
|
+
lastError?: string;
|
|
134
|
+
};
|
|
84
135
|
}
|
|
85
136
|
|
|
86
137
|
const legalDataGrantV2StatusTransitions: Record<string, Record<string, true>> = {
|
|
@@ -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.
|
|
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.
|
|
11
11
|
*/
|
|
12
12
|
export type IntegrationErrorCode =
|
|
13
13
|
| 'USER_CONNECTION_REQUIRED'
|
|
@@ -29,7 +29,15 @@ export type IntegrationErrorCode =
|
|
|
29
29
|
| 'APP_ACCESS_REQUIRED'
|
|
30
30
|
| 'GRANT_DENIED'
|
|
31
31
|
| 'TOKEN_REFRESH_PENDING'
|
|
32
|
-
| 'REQUEST_PENDING'
|
|
32
|
+
| 'REQUEST_PENDING'
|
|
33
|
+
| 'INPUT_INVALID'
|
|
34
|
+
| 'IDEMPOTENCY_KEY_REQUIRED'
|
|
35
|
+
| 'SEND_IN_PROGRESS'
|
|
36
|
+
| 'CONNECTION_NOT_FOUND'
|
|
37
|
+
| 'APP_NOT_FOUND'
|
|
38
|
+
| 'GRANT_NOT_FOUND'
|
|
39
|
+
| 'AUTH_REQUIRED'
|
|
40
|
+
| 'UNAVAILABLE';
|
|
33
41
|
|
|
34
42
|
/**
|
|
35
43
|
* The `error` member of an integration failure response `{error, requestId}` on the execute/connect routes; the SDK maps it onto HarbourError(category, message, details). `category` is the app-SDK HarbourErrorCategory (not the deployment pipeline's dot-path error-category registry), with the HTTP status fixed per category: 401 AUTH_REQUIRED, 403 FORBIDDEN (denied/revoked/expired), 404 NOT_FOUND (unknown app/connection/resource), 409 CONFLICT (pending provisioning/consent/concurrent request), 400 or 413 VALIDATION_FAILED (invalid input / size overflow), 429 RATE_LIMITED (+ Retry-After header), 503 UNAVAILABLE. `details.code` is the closed stable reason set; `message` is human-facing and never carries provider error bodies, tokens or SQL.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Code generated by tools/gen/gen.mjs from schema/*.json. DO NOT EDIT.
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Body of POST /_harbour/integrations/execute (app SDK), forwarded unchanged to the governance development and runtime execute routes. Exactly one declared operation against one declared resource. The identity mode is NOT a caller field: it is fixed by the operation 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.
|
|
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.
|
|
6
6
|
*/
|
|
7
7
|
export type IntegrationExecuteRequest = {
|
|
8
8
|
/**
|
|
@@ -10,9 +10,15 @@ export type IntegrationExecuteRequest = {
|
|
|
10
10
|
*/
|
|
11
11
|
connection: string;
|
|
12
12
|
/**
|
|
13
|
-
* Closed operation set. Extending it is a contracts change.
|
|
13
|
+
* Closed operation set. Extending it is a contracts change. gmail.message.send (0.50.0) sends one plain-text email as the signed-in person: a send like slack.message.post (explicit Send, idempotencyKey, write receipt) under user identity.
|
|
14
14
|
*/
|
|
15
|
-
operation:
|
|
15
|
+
operation:
|
|
16
|
+
| 'slack.channel.history'
|
|
17
|
+
| 'slack.message.post'
|
|
18
|
+
| 'gmail.thread.list'
|
|
19
|
+
| 'gmail.message.read'
|
|
20
|
+
| 'gmail.message.send'
|
|
21
|
+
| 'warehouse.view.read';
|
|
16
22
|
/**
|
|
17
23
|
* Tenant-wide logical alias of a connection (governance sourceId) or of a resource under it.
|
|
18
24
|
*/
|
|
@@ -20,6 +26,21 @@ export type IntegrationExecuteRequest = {
|
|
|
20
26
|
input: {
|
|
21
27
|
limit?: number;
|
|
22
28
|
text?: string;
|
|
29
|
+
/**
|
|
30
|
+
* gmail.message.read: the Gmail message id a gmail.thread.list result named.
|
|
31
|
+
*/
|
|
32
|
+
messageId?: string;
|
|
33
|
+
to?: EmailAddressList;
|
|
34
|
+
cc?: EmailAddressList1;
|
|
35
|
+
bcc?: EmailAddressList2;
|
|
36
|
+
/**
|
|
37
|
+
* gmail.message.send: one-line subject (no line breaks).
|
|
38
|
+
*/
|
|
39
|
+
subject?: string;
|
|
40
|
+
/**
|
|
41
|
+
* gmail.message.send: a Gmail message id (from gmail.thread.list / gmail.message.read) the email replies to; the send joins that thread.
|
|
42
|
+
*/
|
|
43
|
+
replyToMessageId?: string;
|
|
23
44
|
/**
|
|
24
45
|
* @minItems 1
|
|
25
46
|
* @maxItems 32
|
|
@@ -27,7 +48,32 @@ export type IntegrationExecuteRequest = {
|
|
|
27
48
|
columns?: string[];
|
|
28
49
|
};
|
|
29
50
|
/**
|
|
30
|
-
* 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.
|
|
51
|
+
* 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.
|
|
31
52
|
*/
|
|
32
53
|
idempotencyKey?: string;
|
|
33
54
|
};
|
|
55
|
+
/**
|
|
56
|
+
* One bare email address (no display name).
|
|
57
|
+
*/
|
|
58
|
+
export type EmailAddress = string;
|
|
59
|
+
/**
|
|
60
|
+
* gmail.message.send: primary recipients.
|
|
61
|
+
*
|
|
62
|
+
* @minItems 1
|
|
63
|
+
* @maxItems 50
|
|
64
|
+
*/
|
|
65
|
+
export type EmailAddressList = EmailAddress[];
|
|
66
|
+
/**
|
|
67
|
+
* gmail.message.send: carbon-copy recipients.
|
|
68
|
+
*
|
|
69
|
+
* @minItems 1
|
|
70
|
+
* @maxItems 50
|
|
71
|
+
*/
|
|
72
|
+
export type EmailAddressList1 = EmailAddress[];
|
|
73
|
+
/**
|
|
74
|
+
* gmail.message.send: blind-copy recipients.
|
|
75
|
+
*
|
|
76
|
+
* @minItems 1
|
|
77
|
+
* @maxItems 50
|
|
78
|
+
*/
|
|
79
|
+
export type EmailAddressList2 = EmailAddress[];
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Code generated by tools/gen/gen.mjs from schema/*.json. DO NOT EDIT.
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* The `data` member of a successful execute response `{data, requestId}`; the SDK unwraps `data`. Exactly one of the
|
|
5
|
+
* The `data` member of a successful execute response `{data, requestId}`; the SDK unwraps `data`. Exactly one of the six operation results: slack.channel.history -> {messages, hasMore} (plain text/IDs/time only, never the provider payload); slack.message.post -> {messageId}; gmail.thread.list -> {threads, hasMore} (newest inbox threads; each `messageId` is the thread's newest message, what gmail.message.read takes); gmail.message.read -> {id, threadId, subject, from, to, date, text, truncated} (headers plus the plain text with html stripped, cut at 16 KiB with `truncated` set); gmail.message.send -> {messageId, threadId} (the Gmail ids of the sent message and its thread); warehouse.view.read -> {rows} (decimal/64-bit values as strings, dates as ISO strings). An unknown send outcome is never a result; it is an error with details.code SEND_OUTCOME_UNKNOWN. Results over 1 MiB are rejected, not truncated.
|
|
6
6
|
*/
|
|
7
7
|
export type IntegrationExecuteResult = {
|
|
8
8
|
/**
|
|
@@ -11,6 +11,18 @@ export type IntegrationExecuteResult = {
|
|
|
11
11
|
messages?: ChannelMessage[];
|
|
12
12
|
hasMore?: boolean;
|
|
13
13
|
messageId?: string;
|
|
14
|
+
/**
|
|
15
|
+
* @maxItems 15
|
|
16
|
+
*/
|
|
17
|
+
threads?: GmailThread[];
|
|
18
|
+
id?: string;
|
|
19
|
+
threadId?: string;
|
|
20
|
+
subject?: string;
|
|
21
|
+
from?: string;
|
|
22
|
+
to?: string;
|
|
23
|
+
date?: string;
|
|
24
|
+
text?: string;
|
|
25
|
+
truncated?: boolean;
|
|
14
26
|
/**
|
|
15
27
|
* @maxItems 1000
|
|
16
28
|
*/
|
|
@@ -25,3 +37,11 @@ export interface ChannelMessage {
|
|
|
25
37
|
authorId: string;
|
|
26
38
|
createdAt: string;
|
|
27
39
|
}
|
|
40
|
+
export interface GmailThread {
|
|
41
|
+
id: string;
|
|
42
|
+
messageId: string;
|
|
43
|
+
subject: string;
|
|
44
|
+
from: string;
|
|
45
|
+
date: string;
|
|
46
|
+
snippet: string;
|
|
47
|
+
}
|