@mdbase-dev/connect-protocol 0.1.0-beta.100
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/LICENSE +21 -0
- package/dist/application-authorization.d.ts +32 -0
- package/dist/application-authorization.js +207 -0
- package/dist/capabilities.d.ts +73 -0
- package/dist/capabilities.js +92 -0
- package/dist/collection-setup.d.ts +90 -0
- package/dist/collection-setup.js +1 -0
- package/dist/compatibility.d.ts +37 -0
- package/dist/compatibility.js +65 -0
- package/dist/connect-problems.generated.d.ts +1388 -0
- package/dist/connect-problems.generated.js +574 -0
- package/dist/files.d.ts +269 -0
- package/dist/files.js +426 -0
- package/dist/index.d.ts +878 -0
- package/dist/index.js +94 -0
- package/dist/manifest.d.ts +62 -0
- package/dist/manifest.js +527 -0
- package/dist/mutation-fingerprint.d.ts +6 -0
- package/dist/mutation-fingerprint.js +116 -0
- package/dist/operations.d.ts +15 -0
- package/dist/operations.js +194 -0
- package/dist/type-packs.d.ts +133 -0
- package/dist/type-packs.js +30 -0
- package/package.json +42 -0
- package/schemas/application-capability-catalog.v1.json +40 -0
- package/schemas/application-capability-catalog.v2.json +68 -0
- package/schemas/catalog.json +17 -0
- package/schemas/connect-problem.v1.schema.json +5132 -0
- package/schemas/connect-problems.v1.catalog.json +351 -0
- package/schemas/connect-protocol.v1.schema.json +800 -0
- package/schemas/data-contract.schema.json +163 -0
- package/schemas/encrypted-relay.v1.schema.json +63 -0
- package/schemas/encrypted-relay.v2.schema.json +63 -0
- package/schemas/encrypted-relay.v3.schema.json +64 -0
- package/schemas/files.v1.schema.json +507 -0
- package/schemas/interop/v0.1/action-cancellation.schema.json +6 -0
- package/schemas/interop/v0.1/action-invocation.schema.json +6 -0
- package/schemas/interop/v0.1/action-outcome.schema.json +6 -0
- package/schemas/interop/v0.1/action-provider-declaration.schema.json +6 -0
- package/schemas/interop/v0.1/action-request.schema.json +6 -0
- package/schemas/interop/v0.1/conformance-claim.schema.json +6 -0
- package/schemas/interop/v0.1/event-source-declaration.schema.json +6 -0
- package/schemas/interop/v0.1/event.schema.json +6 -0
- package/schemas/interop/v0.1/profile.schema.json +456 -0
- package/schemas/mdbase-app.legacy-v1.schema.json +405 -0
- package/schemas/mdbase-app.schema.json +381 -0
- package/schemas/mdbase-query.v0.3.schema.json +171 -0
- package/schemas/notification-webhook.v1.schema.json +56 -0
- package/schemas/operation-catalog.v1.json +42 -0
- package/schemas/operation-mutation-classification.v1.json +537 -0
- package/schemas/sync.v1.schema.json +483 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 mdbase connect contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ApplicationFileRequest } from "./files.js";
|
|
2
|
+
import { type CollectionOperation } from "./operations.js";
|
|
3
|
+
import type { AuthorizationBindingProtocolVersion, ConnectContractRequirements } from "./compatibility.js";
|
|
4
|
+
export type ApplicationAuthorizationFlow = "authorization_code" | "device_code";
|
|
5
|
+
export interface ApplicationAuthorizationBinding {
|
|
6
|
+
protocol_version: AuthorizationBindingProtocolVersion;
|
|
7
|
+
authorization_id: string;
|
|
8
|
+
application_id: string;
|
|
9
|
+
application_declaration_id: string;
|
|
10
|
+
application_manifest_digest: string;
|
|
11
|
+
application_installation_id: string;
|
|
12
|
+
installation_signing_public_key: string;
|
|
13
|
+
grant_agreement_public_key: string;
|
|
14
|
+
grant_signing_public_key: string;
|
|
15
|
+
flow: ApplicationAuthorizationFlow;
|
|
16
|
+
authorization_nonce: string;
|
|
17
|
+
issued_at: string;
|
|
18
|
+
expires_at: string;
|
|
19
|
+
redirect_uri?: string;
|
|
20
|
+
state?: string;
|
|
21
|
+
code_challenge: string;
|
|
22
|
+
contracts: ConnectContractRequirements;
|
|
23
|
+
requested_operations: CollectionOperation[];
|
|
24
|
+
requested_files?: ApplicationFileRequest;
|
|
25
|
+
collection_id?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface ApplicationAuthorizationProof {
|
|
28
|
+
binding: ApplicationAuthorizationBinding;
|
|
29
|
+
signature: string;
|
|
30
|
+
}
|
|
31
|
+
export declare function applicationInstallationIdFromPublicKey(signingPublicKey: string): Promise<string>;
|
|
32
|
+
export declare function authorizationSigningMessage(binding: ApplicationAuthorizationBinding): Uint8Array;
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { isMutatingOperation } from "./operations.js";
|
|
2
|
+
import { AUTHORIZATION_BINDING_PROTOCOL_VERSION, LEGACY_AUTHORIZATION_BINDING_PROTOCOL_VERSION, OPERATION_TRANSPORT_PROTOCOL_VERSION, isSupportedAuthorizationBinding, isSupportedOperationTransport } from "./compatibility.js";
|
|
3
|
+
const INSTALLATION_ID_DOMAIN = new TextEncoder().encode("mdbase-connect application installation id v2\0");
|
|
4
|
+
const AUTHORIZATION_PROOF_V4_DOMAIN = new TextEncoder().encode("mdbase-connect application authorization proof v4\0");
|
|
5
|
+
const AUTHORIZATION_PROOF_V5_DOMAIN = new TextEncoder().encode("mdbase-connect application authorization proof v5\0");
|
|
6
|
+
export async function applicationInstallationIdFromPublicKey(signingPublicKey) {
|
|
7
|
+
const signing = publicKey(signingPublicKey);
|
|
8
|
+
const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", concat([INSTALLATION_ID_DOMAIN, field(signing)])));
|
|
9
|
+
const id = digest.slice(0, 16);
|
|
10
|
+
id[6] = (id[6] & 0x0f) | 0x80;
|
|
11
|
+
id[8] = (id[8] & 0x3f) | 0x80;
|
|
12
|
+
return [
|
|
13
|
+
hex(id.slice(0, 4)),
|
|
14
|
+
hex(id.slice(4, 6)),
|
|
15
|
+
hex(id.slice(6, 8)),
|
|
16
|
+
hex(id.slice(8, 10)),
|
|
17
|
+
hex(id.slice(10, 16))
|
|
18
|
+
].join("-");
|
|
19
|
+
}
|
|
20
|
+
export function authorizationSigningMessage(binding) {
|
|
21
|
+
const keys = [
|
|
22
|
+
publicKey(binding.installation_signing_public_key),
|
|
23
|
+
publicKey(binding.grant_agreement_public_key),
|
|
24
|
+
publicKey(binding.grant_signing_public_key)
|
|
25
|
+
];
|
|
26
|
+
if (keys.some((key, index) => keys.slice(index + 1).some((other) => equalBytes(key, other)))) {
|
|
27
|
+
throw new Error("Application authorization keys must be distinct.");
|
|
28
|
+
}
|
|
29
|
+
const nonce = canonicalBase64(binding.authorization_nonce);
|
|
30
|
+
const challenge = canonicalBase64(binding.code_challenge);
|
|
31
|
+
const contracts = binding.contracts;
|
|
32
|
+
const recovery = contracts.operation_transport_recovery ?? [];
|
|
33
|
+
const requiresDurableMutation = binding.requested_operations.some((operation) => isMutatingOperation(operation, { action: "mutate" }))
|
|
34
|
+
|| binding.requested_files?.actions.some((action) => !["list", "read"].includes(action)) === true;
|
|
35
|
+
if (!isSupportedAuthorizationBinding(binding.protocol_version)
|
|
36
|
+
|| nonce.byteLength !== 32
|
|
37
|
+
|| !/^[a-z][a-z0-9]*(?:[._-][a-z0-9]+)+$/u.test(binding.application_declaration_id)
|
|
38
|
+
|| !/^[0-9a-f]{64}$/u.test(binding.application_manifest_digest)
|
|
39
|
+
|| !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?Z$/u.test(binding.issued_at)
|
|
40
|
+
|| !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?Z$/u.test(binding.expires_at)
|
|
41
|
+
|| challenge.byteLength !== 32
|
|
42
|
+
|| ![
|
|
43
|
+
contracts.operation_transport,
|
|
44
|
+
contracts.authorization_binding,
|
|
45
|
+
contracts.semantic_capabilities,
|
|
46
|
+
contracts.durable_mutation ?? 1
|
|
47
|
+
].every((version) => Number.isInteger(version) && version > 0)
|
|
48
|
+
|| contracts.authorization_binding !== binding.protocol_version
|
|
49
|
+
|| !isSupportedOperationTransport(contracts.operation_transport)
|
|
50
|
+
|| (binding.protocol_version === AUTHORIZATION_BINDING_PROTOCOL_VERSION
|
|
51
|
+
&& contracts.operation_transport !== OPERATION_TRANSPORT_PROTOCOL_VERSION)
|
|
52
|
+
|| recovery.some((version) => !isSupportedOperationTransport(version)
|
|
53
|
+
|| version === contracts.operation_transport)
|
|
54
|
+
|| new Set(recovery).size !== recovery.length
|
|
55
|
+
|| (binding.protocol_version === LEGACY_AUTHORIZATION_BINDING_PROTOCOL_VERSION
|
|
56
|
+
&& recovery.length > 0)
|
|
57
|
+
|| (recovery.length > 0 && !requiresDurableMutation)
|
|
58
|
+
|| (requiresDurableMutation
|
|
59
|
+
? contracts.durable_mutation !== 1
|
|
60
|
+
: contracts.durable_mutation !== undefined)
|
|
61
|
+
|| (binding.requested_operations.length === 0 && binding.requested_files === undefined)
|
|
62
|
+
|| new Set(binding.requested_operations).size !== binding.requested_operations.length
|
|
63
|
+
|| binding.requested_operations.some((operation) => !operation || operation.includes("\0"))
|
|
64
|
+
|| (binding.flow === "authorization_code"
|
|
65
|
+
? binding.redirect_uri === undefined || binding.state === undefined
|
|
66
|
+
: binding.flow === "device_code"
|
|
67
|
+
? binding.redirect_uri !== undefined || binding.state !== undefined
|
|
68
|
+
: true)) {
|
|
69
|
+
throw new Error("Application authorization binding is invalid.");
|
|
70
|
+
}
|
|
71
|
+
validateRequestedFiles(binding.requested_files);
|
|
72
|
+
const operationFields = binding.requested_operations.map((operation) => field(new TextEncoder().encode(operation)));
|
|
73
|
+
return concat([
|
|
74
|
+
binding.protocol_version === AUTHORIZATION_BINDING_PROTOCOL_VERSION
|
|
75
|
+
? AUTHORIZATION_PROOF_V5_DOMAIN
|
|
76
|
+
: AUTHORIZATION_PROOF_V4_DOMAIN,
|
|
77
|
+
u32(binding.protocol_version),
|
|
78
|
+
field(uuidBytes(binding.application_id)),
|
|
79
|
+
field(uuidBytes(binding.authorization_id)),
|
|
80
|
+
field(new TextEncoder().encode(binding.application_declaration_id)),
|
|
81
|
+
field(hexBytes(binding.application_manifest_digest)),
|
|
82
|
+
field(uuidBytes(binding.application_installation_id)),
|
|
83
|
+
...keys.map(field),
|
|
84
|
+
field(new TextEncoder().encode(binding.flow)),
|
|
85
|
+
field(nonce),
|
|
86
|
+
field(new TextEncoder().encode(binding.issued_at)),
|
|
87
|
+
field(new TextEncoder().encode(binding.expires_at)),
|
|
88
|
+
optionalString(binding.redirect_uri),
|
|
89
|
+
optionalString(binding.state),
|
|
90
|
+
field(new TextEncoder().encode(binding.code_challenge)),
|
|
91
|
+
u32(contracts.operation_transport),
|
|
92
|
+
...(binding.protocol_version === AUTHORIZATION_BINDING_PROTOCOL_VERSION
|
|
93
|
+
? [u32(recovery.length), ...recovery.map(u32)]
|
|
94
|
+
: []),
|
|
95
|
+
u32(contracts.authorization_binding),
|
|
96
|
+
u32(contracts.semantic_capabilities),
|
|
97
|
+
optionalU32(contracts.durable_mutation),
|
|
98
|
+
u32(operationFields.length),
|
|
99
|
+
...operationFields,
|
|
100
|
+
requestedFiles(binding.requested_files),
|
|
101
|
+
optionalUuid(binding.collection_id)
|
|
102
|
+
]);
|
|
103
|
+
}
|
|
104
|
+
function optionalU32(value) {
|
|
105
|
+
return value === undefined
|
|
106
|
+
? new Uint8Array(1)
|
|
107
|
+
: concat([new Uint8Array([1]), u32(value)]);
|
|
108
|
+
}
|
|
109
|
+
function validateRequestedFiles(files) {
|
|
110
|
+
if (files === undefined)
|
|
111
|
+
return;
|
|
112
|
+
if (files.actions.length === 0
|
|
113
|
+
|| new Set(files.actions).size !== files.actions.length
|
|
114
|
+
|| (files.scope.kind === "selected_folders"
|
|
115
|
+
&& (files.scope.folders.length === 0
|
|
116
|
+
|| new Set(files.scope.folders).size !== files.scope.folders.length
|
|
117
|
+
|| files.scope.folders.some((folder) => !folder || folder.includes("\0"))))) {
|
|
118
|
+
throw new Error("Application authorization file request is invalid.");
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function requestedFiles(files) {
|
|
122
|
+
if (files === undefined)
|
|
123
|
+
return new Uint8Array(1);
|
|
124
|
+
const actions = files.actions.map((action) => field(new TextEncoder().encode(action)));
|
|
125
|
+
return concat([
|
|
126
|
+
new Uint8Array([1]),
|
|
127
|
+
u32(actions.length),
|
|
128
|
+
...actions,
|
|
129
|
+
field(new TextEncoder().encode(files.scope.kind)),
|
|
130
|
+
...(files.scope.kind === "selected_folders"
|
|
131
|
+
? [
|
|
132
|
+
u32(files.scope.folders.length),
|
|
133
|
+
...files.scope.folders.map((folder) => field(new TextEncoder().encode(folder)))
|
|
134
|
+
]
|
|
135
|
+
: [])
|
|
136
|
+
]);
|
|
137
|
+
}
|
|
138
|
+
function optionalString(value) {
|
|
139
|
+
return value === undefined
|
|
140
|
+
? new Uint8Array(1)
|
|
141
|
+
: concat([new Uint8Array([1]), field(new TextEncoder().encode(value))]);
|
|
142
|
+
}
|
|
143
|
+
function optionalUuid(value) {
|
|
144
|
+
return value === undefined
|
|
145
|
+
? new Uint8Array(1)
|
|
146
|
+
: concat([new Uint8Array([1]), field(uuidBytes(value))]);
|
|
147
|
+
}
|
|
148
|
+
function publicKey(value) {
|
|
149
|
+
const key = canonicalBase64(value);
|
|
150
|
+
if (key.byteLength !== 65 || key[0] !== 4) {
|
|
151
|
+
throw new Error("Application authorization public key is invalid.");
|
|
152
|
+
}
|
|
153
|
+
return key;
|
|
154
|
+
}
|
|
155
|
+
function canonicalBase64(value) {
|
|
156
|
+
if (!/^[A-Za-z0-9_-]+$/u.test(value))
|
|
157
|
+
throw new Error("Invalid base64url value.");
|
|
158
|
+
const normalized = value.replaceAll("-", "+").replaceAll("_", "/");
|
|
159
|
+
const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, "=");
|
|
160
|
+
const decoded = Uint8Array.from(atob(padded), (character) => character.charCodeAt(0));
|
|
161
|
+
if (base64Url(decoded) !== value)
|
|
162
|
+
throw new Error("Non-canonical base64url value.");
|
|
163
|
+
return decoded;
|
|
164
|
+
}
|
|
165
|
+
function uuidBytes(value) {
|
|
166
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu.test(value)) {
|
|
167
|
+
throw new Error("Invalid UUID value.");
|
|
168
|
+
}
|
|
169
|
+
const compact = value.replaceAll("-", "");
|
|
170
|
+
return hexBytes(compact);
|
|
171
|
+
}
|
|
172
|
+
function hexBytes(value) {
|
|
173
|
+
if (!/^[0-9a-f]+$/u.test(value) || value.length % 2 !== 0) {
|
|
174
|
+
throw new Error("Invalid hexadecimal value.");
|
|
175
|
+
}
|
|
176
|
+
return Uint8Array.from({ length: value.length / 2 }, (_, index) => Number.parseInt(value.slice(index * 2, index * 2 + 2), 16));
|
|
177
|
+
}
|
|
178
|
+
function field(value) {
|
|
179
|
+
return concat([u32(value.byteLength), value]);
|
|
180
|
+
}
|
|
181
|
+
function u32(value) {
|
|
182
|
+
const output = new Uint8Array(4);
|
|
183
|
+
new DataView(output.buffer).setUint32(0, value, false);
|
|
184
|
+
return output;
|
|
185
|
+
}
|
|
186
|
+
function concat(values) {
|
|
187
|
+
const output = new Uint8Array(values.reduce((length, value) => length + value.byteLength, 0));
|
|
188
|
+
let offset = 0;
|
|
189
|
+
for (const value of values) {
|
|
190
|
+
output.set(value, offset);
|
|
191
|
+
offset += value.byteLength;
|
|
192
|
+
}
|
|
193
|
+
return output;
|
|
194
|
+
}
|
|
195
|
+
function base64Url(value) {
|
|
196
|
+
return btoa(String.fromCharCode(...value))
|
|
197
|
+
.replaceAll("+", "-")
|
|
198
|
+
.replaceAll("/", "_")
|
|
199
|
+
.replace(/=+$/u, "");
|
|
200
|
+
}
|
|
201
|
+
function equalBytes(left, right) {
|
|
202
|
+
return left.byteLength === right.byteLength
|
|
203
|
+
&& left.every((value, index) => value === right[index]);
|
|
204
|
+
}
|
|
205
|
+
function hex(value) {
|
|
206
|
+
return [...value].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
207
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { CollectionOperation } from "./operations.js";
|
|
2
|
+
export declare const APPLICATION_CAPABILITY_CONTRACT_VERSION: 2;
|
|
3
|
+
export declare const APPLICATION_CAPABILITY_DEFINITIONS: {
|
|
4
|
+
readonly "collection.read": readonly ["describe", "changes", "read", "query", "list_views", "execute_view", "read_view_source", "validate", "read_type"];
|
|
5
|
+
readonly "records.create": readonly ["create"];
|
|
6
|
+
readonly "records.edit": readonly ["update", "rename"];
|
|
7
|
+
readonly "records.delete": readonly ["delete"];
|
|
8
|
+
readonly "views.manage": readonly ["create_view_source", "update_view_source", "delete_view_source"];
|
|
9
|
+
readonly "definitions.manage": readonly ["create_type", "update_type", "assess_type_pack", "apply_type_pack"];
|
|
10
|
+
readonly "background.schedule": readonly ["list_timers", "put_timer", "cancel_timer", "reconcile_timers"];
|
|
11
|
+
readonly "offline.replica": readonly ["sync"];
|
|
12
|
+
};
|
|
13
|
+
export declare const APPLICATION_SETUP_OPERATIONS: readonly ["assess_collection_setup", "apply_collection_setup"];
|
|
14
|
+
export type ApplicationCapabilityId = keyof typeof APPLICATION_CAPABILITY_DEFINITIONS;
|
|
15
|
+
export interface ApplicationCapabilityRequirements {
|
|
16
|
+
contract_version: typeof APPLICATION_CAPABILITY_CONTRACT_VERSION;
|
|
17
|
+
required: ApplicationCapabilityId[];
|
|
18
|
+
optional?: ApplicationCapabilityId[];
|
|
19
|
+
}
|
|
20
|
+
export declare function operationsForApplicationCapabilities(requirements: ApplicationCapabilityRequirements, options?: {
|
|
21
|
+
includeOptional?: boolean;
|
|
22
|
+
}): CollectionOperation[];
|
|
23
|
+
export declare function capabilityOperations(capability: ApplicationCapabilityId): CollectionOperation[];
|
|
24
|
+
export declare function applicationOperationSelectionIsAtomic(requirements: ApplicationCapabilityRequirements, operations: readonly string[]): boolean;
|
|
25
|
+
export declare const APPLICATION_CAPABILITY_V1_CONTRACT_VERSION: 1;
|
|
26
|
+
export declare const APPLICATION_CAPABILITY_V1_DEFINITIONS: Readonly<{
|
|
27
|
+
readonly "collection.inspect": readonly ["describe"];
|
|
28
|
+
readonly "records.watch": readonly ["changes"];
|
|
29
|
+
readonly "records.read": readonly ["read"];
|
|
30
|
+
readonly "records.query": readonly ["query"];
|
|
31
|
+
readonly "records.validate": readonly ["validate"];
|
|
32
|
+
readonly "records.create": readonly ["create"];
|
|
33
|
+
readonly "records.update": readonly ["update"];
|
|
34
|
+
readonly "records.delete": readonly ["delete"];
|
|
35
|
+
readonly "records.rename": readonly ["rename"];
|
|
36
|
+
readonly "views.list": readonly ["list_views"];
|
|
37
|
+
readonly "views.execute": readonly ["execute_view"];
|
|
38
|
+
readonly "views.source.read": readonly ["read_view_source"];
|
|
39
|
+
readonly "views.source.create": readonly ["create_view_source"];
|
|
40
|
+
readonly "views.source.update": readonly ["update_view_source"];
|
|
41
|
+
readonly "views.source.delete": readonly ["delete_view_source"];
|
|
42
|
+
readonly "definitions.contracts.current": readonly [];
|
|
43
|
+
readonly "definitions.read": readonly ["read_type"];
|
|
44
|
+
readonly "definitions.create": readonly ["create_type"];
|
|
45
|
+
readonly "definitions.update": readonly ["update_type"];
|
|
46
|
+
readonly "definitions.type-pack.inspect": readonly ["assess_type_pack"];
|
|
47
|
+
readonly "definitions.type-pack.apply": readonly ["assess_type_pack", "apply_type_pack"];
|
|
48
|
+
readonly "collection.setup.apply": readonly ["assess_collection_setup", "apply_collection_setup"];
|
|
49
|
+
readonly "timers.list": readonly ["list_timers"];
|
|
50
|
+
readonly "timers.put": readonly ["put_timer"];
|
|
51
|
+
readonly "timers.cancel": readonly ["cancel_timer"];
|
|
52
|
+
readonly "timers.reconcile": readonly ["reconcile_timers"];
|
|
53
|
+
readonly "sync.offline-replica": readonly ["sync"];
|
|
54
|
+
readonly "notifications.background-delivery": readonly [];
|
|
55
|
+
readonly "files.list": readonly [];
|
|
56
|
+
readonly "files.read": readonly [];
|
|
57
|
+
readonly "files.add": readonly [];
|
|
58
|
+
readonly "files.replace": readonly [];
|
|
59
|
+
readonly "files.move": readonly [];
|
|
60
|
+
readonly "files.delete": readonly [];
|
|
61
|
+
}>;
|
|
62
|
+
export type LegacyApplicationCapabilityId = keyof typeof APPLICATION_CAPABILITY_V1_DEFINITIONS;
|
|
63
|
+
export interface LegacyApplicationCapabilityRequirements {
|
|
64
|
+
contract_version: typeof APPLICATION_CAPABILITY_V1_CONTRACT_VERSION;
|
|
65
|
+
required: LegacyApplicationCapabilityId[];
|
|
66
|
+
optional?: LegacyApplicationCapabilityId[];
|
|
67
|
+
}
|
|
68
|
+
export declare function capabilityOperationsForContractVersion(contractVersion: number, capability: string): CollectionOperation[] | undefined;
|
|
69
|
+
/** Fresh issuance only; never use this ceiling to reject retained authority or terminal replay. */
|
|
70
|
+
export declare const FRESH_APPLICATION_AUTHORIZATION_VERSIONS: readonly [1, 2];
|
|
71
|
+
export declare const APPLICATION_AUTHORIZATION_V2_ISSUANCE_CAPABILITY: "application-authorization-v2-issuance";
|
|
72
|
+
export declare const FRESH_APPLICATION_AUTHORIZATION_CAPABILITIES: readonly string[];
|
|
73
|
+
export declare function permitsFreshApplicationAuthorization(version: number): boolean;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Generated by scripts/generate-application-capabilities.mjs. Do not edit directly.
|
|
2
|
+
export const APPLICATION_CAPABILITY_CONTRACT_VERSION = 2;
|
|
3
|
+
export const APPLICATION_CAPABILITY_DEFINITIONS = {
|
|
4
|
+
"collection.read": ["describe", "changes", "read", "query", "list_views", "execute_view", "read_view_source", "validate", "read_type"],
|
|
5
|
+
"records.create": ["create"],
|
|
6
|
+
"records.edit": ["update", "rename"],
|
|
7
|
+
"records.delete": ["delete"],
|
|
8
|
+
"views.manage": ["create_view_source", "update_view_source", "delete_view_source"],
|
|
9
|
+
"definitions.manage": ["create_type", "update_type", "assess_type_pack", "apply_type_pack"],
|
|
10
|
+
"background.schedule": ["list_timers", "put_timer", "cancel_timer", "reconcile_timers"],
|
|
11
|
+
"offline.replica": ["sync"]
|
|
12
|
+
};
|
|
13
|
+
export const APPLICATION_SETUP_OPERATIONS = [
|
|
14
|
+
"assess_collection_setup",
|
|
15
|
+
"apply_collection_setup"
|
|
16
|
+
];
|
|
17
|
+
export function operationsForApplicationCapabilities(requirements, options = {}) {
|
|
18
|
+
const capabilities = options.includeOptional === false
|
|
19
|
+
? requirements.required
|
|
20
|
+
: [...requirements.required, ...(requirements.optional ?? [])];
|
|
21
|
+
return [...new Set(capabilities.flatMap((capability) => APPLICATION_CAPABILITY_DEFINITIONS[capability]))];
|
|
22
|
+
}
|
|
23
|
+
export function capabilityOperations(capability) {
|
|
24
|
+
return [...APPLICATION_CAPABILITY_DEFINITIONS[capability]];
|
|
25
|
+
}
|
|
26
|
+
export function applicationOperationSelectionIsAtomic(requirements, operations) {
|
|
27
|
+
const selected = new Set(operations);
|
|
28
|
+
const declared = [...requirements.required, ...(requirements.optional ?? [])];
|
|
29
|
+
const allowed = new Set(declared.flatMap((capability) => APPLICATION_CAPABILITY_DEFINITIONS[capability]));
|
|
30
|
+
if ([...selected].some((operation) => !allowed.has(operation))) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
return declared.every((capability) => {
|
|
34
|
+
const capabilityOperations = APPLICATION_CAPABILITY_DEFINITIONS[capability];
|
|
35
|
+
const count = capabilityOperations.filter((operation) => selected.has(operation)).length;
|
|
36
|
+
return requirements.required.includes(capability)
|
|
37
|
+
? count === capabilityOperations.length
|
|
38
|
+
: count === 0 || count === capabilityOperations.length;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
// Frozen predecessor catalog. Lookup availability does not imply declaration acceptance.
|
|
42
|
+
export const APPLICATION_CAPABILITY_V1_CONTRACT_VERSION = 1;
|
|
43
|
+
export const APPLICATION_CAPABILITY_V1_DEFINITIONS = Object.freeze({
|
|
44
|
+
"collection.inspect": Object.freeze(["describe"]),
|
|
45
|
+
"records.watch": Object.freeze(["changes"]),
|
|
46
|
+
"records.read": Object.freeze(["read"]),
|
|
47
|
+
"records.query": Object.freeze(["query"]),
|
|
48
|
+
"records.validate": Object.freeze(["validate"]),
|
|
49
|
+
"records.create": Object.freeze(["create"]),
|
|
50
|
+
"records.update": Object.freeze(["update"]),
|
|
51
|
+
"records.delete": Object.freeze(["delete"]),
|
|
52
|
+
"records.rename": Object.freeze(["rename"]),
|
|
53
|
+
"views.list": Object.freeze(["list_views"]),
|
|
54
|
+
"views.execute": Object.freeze(["execute_view"]),
|
|
55
|
+
"views.source.read": Object.freeze(["read_view_source"]),
|
|
56
|
+
"views.source.create": Object.freeze(["create_view_source"]),
|
|
57
|
+
"views.source.update": Object.freeze(["update_view_source"]),
|
|
58
|
+
"views.source.delete": Object.freeze(["delete_view_source"]),
|
|
59
|
+
"definitions.contracts.current": Object.freeze([]),
|
|
60
|
+
"definitions.read": Object.freeze(["read_type"]),
|
|
61
|
+
"definitions.create": Object.freeze(["create_type"]),
|
|
62
|
+
"definitions.update": Object.freeze(["update_type"]),
|
|
63
|
+
"definitions.type-pack.inspect": Object.freeze(["assess_type_pack"]),
|
|
64
|
+
"definitions.type-pack.apply": Object.freeze(["assess_type_pack", "apply_type_pack"]),
|
|
65
|
+
"collection.setup.apply": Object.freeze(["assess_collection_setup", "apply_collection_setup"]),
|
|
66
|
+
"timers.list": Object.freeze(["list_timers"]),
|
|
67
|
+
"timers.put": Object.freeze(["put_timer"]),
|
|
68
|
+
"timers.cancel": Object.freeze(["cancel_timer"]),
|
|
69
|
+
"timers.reconcile": Object.freeze(["reconcile_timers"]),
|
|
70
|
+
"sync.offline-replica": Object.freeze(["sync"]),
|
|
71
|
+
"notifications.background-delivery": Object.freeze([]),
|
|
72
|
+
"files.list": Object.freeze([]),
|
|
73
|
+
"files.read": Object.freeze([]),
|
|
74
|
+
"files.add": Object.freeze([]),
|
|
75
|
+
"files.replace": Object.freeze([]),
|
|
76
|
+
"files.move": Object.freeze([]),
|
|
77
|
+
"files.delete": Object.freeze([])
|
|
78
|
+
});
|
|
79
|
+
export function capabilityOperationsForContractVersion(contractVersion, capability) {
|
|
80
|
+
const definitions = contractVersion === 1 ? APPLICATION_CAPABILITY_V1_DEFINITIONS
|
|
81
|
+
: contractVersion === 2 ? APPLICATION_CAPABILITY_DEFINITIONS : undefined;
|
|
82
|
+
if (!definitions || !Object.hasOwn(definitions, capability))
|
|
83
|
+
return undefined;
|
|
84
|
+
return [...definitions[capability]];
|
|
85
|
+
}
|
|
86
|
+
/** Fresh issuance only; never use this ceiling to reject retained authority or terminal replay. */
|
|
87
|
+
export const FRESH_APPLICATION_AUTHORIZATION_VERSIONS = Object.freeze([1, 2]);
|
|
88
|
+
export const APPLICATION_AUTHORIZATION_V2_ISSUANCE_CAPABILITY = "application-authorization-v2-issuance";
|
|
89
|
+
export const FRESH_APPLICATION_AUTHORIZATION_CAPABILITIES = Object.freeze(["application-authorization-v2-issuance"]);
|
|
90
|
+
export function permitsFreshApplicationAuthorization(version) {
|
|
91
|
+
return FRESH_APPLICATION_AUTHORIZATION_VERSIONS.some((supported) => supported === version);
|
|
92
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { ContractSetupChoice, TypePackAssessment, TypePackProvision, TypePackReceipt } from "./type-packs.js";
|
|
2
|
+
export type ConfigurationContributionValue = string | number | boolean | null;
|
|
3
|
+
export interface ConfigurationRequirement {
|
|
4
|
+
id: string;
|
|
5
|
+
path: string;
|
|
6
|
+
predicate: "contains";
|
|
7
|
+
value: ConfigurationContributionValue;
|
|
8
|
+
}
|
|
9
|
+
export interface ConfigurationProvision {
|
|
10
|
+
requirement: string;
|
|
11
|
+
operation: "set_add";
|
|
12
|
+
path: string;
|
|
13
|
+
value: ConfigurationContributionValue;
|
|
14
|
+
}
|
|
15
|
+
export interface ApplicationCollectionSetupRequirements {
|
|
16
|
+
configuration: ConfigurationRequirement[];
|
|
17
|
+
}
|
|
18
|
+
export interface ApplicationCollectionSetupProvisions {
|
|
19
|
+
configuration: ConfigurationProvision[];
|
|
20
|
+
type_packs: TypePackProvision[];
|
|
21
|
+
}
|
|
22
|
+
export interface AssessCollectionSetupInput {
|
|
23
|
+
/** Stable application declaration id, not the server's database id. */
|
|
24
|
+
application_id: string;
|
|
25
|
+
/** Exact canonical digest of the complete registered application declaration. */
|
|
26
|
+
declaration_digest: string;
|
|
27
|
+
requirements: ApplicationCollectionSetupRequirements;
|
|
28
|
+
provisions: ApplicationCollectionSetupProvisions;
|
|
29
|
+
/** Collection-owner choices for user-owned contract implementations. */
|
|
30
|
+
contract_setups?: ContractSetupChoice[];
|
|
31
|
+
/** Digest-pinned consent to adopt unmanaged managed resources, keyed by pack id and target. */
|
|
32
|
+
type_pack_adoptions?: Record<string, Record<string, string>>;
|
|
33
|
+
}
|
|
34
|
+
export interface ApplyCollectionSetupInput extends AssessCollectionSetupInput {
|
|
35
|
+
expected_assessment_digest: string;
|
|
36
|
+
expected_collection_revision: string;
|
|
37
|
+
expected_provision_digest: string;
|
|
38
|
+
allow_type_pack_downgrades?: string[];
|
|
39
|
+
}
|
|
40
|
+
export interface ConfigurationSetupConflict {
|
|
41
|
+
code: "configuration_path_conflict" | "configuration_type_conflict";
|
|
42
|
+
path: string;
|
|
43
|
+
expected: "mapping" | "sequence";
|
|
44
|
+
observed: "null" | "boolean" | "number" | "string" | "sequence" | "mapping" | "tagged";
|
|
45
|
+
message: string;
|
|
46
|
+
}
|
|
47
|
+
export interface ConfigurationSetupAssessment {
|
|
48
|
+
requirement: string;
|
|
49
|
+
path: string;
|
|
50
|
+
value: ConfigurationContributionValue;
|
|
51
|
+
action: "current" | "add" | "conflict";
|
|
52
|
+
conflict?: ConfigurationSetupConflict;
|
|
53
|
+
}
|
|
54
|
+
export interface CollectionSetupAssessment {
|
|
55
|
+
status: "current" | "provision" | "conflict";
|
|
56
|
+
applicable: boolean;
|
|
57
|
+
application_id: string;
|
|
58
|
+
declaration_digest: string;
|
|
59
|
+
provision_digest: string;
|
|
60
|
+
collection_revision: string;
|
|
61
|
+
final_collection_revision: string;
|
|
62
|
+
baseline_diagnostic_count: number;
|
|
63
|
+
final_diagnostic_count: number;
|
|
64
|
+
resolved_diagnostic_count: number;
|
|
65
|
+
introduced_diagnostic_count: number;
|
|
66
|
+
baseline_diagnostic_digest: string;
|
|
67
|
+
configuration: ConfigurationSetupAssessment[];
|
|
68
|
+
type_packs: TypePackAssessment[];
|
|
69
|
+
final_resource_revisions: Record<string, string>;
|
|
70
|
+
assessment_digest: string;
|
|
71
|
+
}
|
|
72
|
+
export interface ConfigurationContributionReceipt {
|
|
73
|
+
requirement: string;
|
|
74
|
+
path: string;
|
|
75
|
+
value: ConfigurationContributionValue;
|
|
76
|
+
}
|
|
77
|
+
export interface CollectionSetupReceipt {
|
|
78
|
+
application_id: string;
|
|
79
|
+
declaration_digest: string;
|
|
80
|
+
provision_digest: string;
|
|
81
|
+
assessment_digest: string;
|
|
82
|
+
collection_revision: string;
|
|
83
|
+
configuration: ConfigurationContributionReceipt[];
|
|
84
|
+
type_packs: TypePackReceipt[];
|
|
85
|
+
cleanup_deferred: boolean;
|
|
86
|
+
}
|
|
87
|
+
export interface CollectionSetupApplyResult {
|
|
88
|
+
assessment: CollectionSetupAssessment;
|
|
89
|
+
receipt: CollectionSetupReceipt;
|
|
90
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ApplicationFileRequest } from "./files.js";
|
|
2
|
+
import { type CollectionOperation } from "./operations.js";
|
|
3
|
+
export declare const OPERATION_TRANSPORT_PROTOCOL_VERSION: 3;
|
|
4
|
+
export declare const LEGACY_OPERATION_TRANSPORT_PROTOCOL_VERSION: 2;
|
|
5
|
+
export declare const SUPPORTED_OPERATION_TRANSPORT_PROTOCOL_VERSIONS: readonly [3, 2];
|
|
6
|
+
export type OperationTransportProtocolVersion = typeof SUPPORTED_OPERATION_TRANSPORT_PROTOCOL_VERSIONS[number];
|
|
7
|
+
export declare const AUTHORIZATION_BINDING_PROTOCOL_VERSION: 5;
|
|
8
|
+
export declare const LEGACY_AUTHORIZATION_BINDING_PROTOCOL_VERSION: 4;
|
|
9
|
+
export declare const SUPPORTED_AUTHORIZATION_BINDING_PROTOCOL_VERSIONS: readonly [5, 4];
|
|
10
|
+
export type AuthorizationBindingProtocolVersion = typeof SUPPORTED_AUTHORIZATION_BINDING_PROTOCOL_VERSIONS[number];
|
|
11
|
+
export declare const SEMANTIC_CAPABILITY_CONTRACT_VERSION: 2;
|
|
12
|
+
export declare const DURABLE_MUTATION_CONTRACT_VERSION: 1;
|
|
13
|
+
export interface ConnectContractRequirements {
|
|
14
|
+
operation_transport: number;
|
|
15
|
+
/** Signed, mutation-only transports retained solely for exact outcome recovery. */
|
|
16
|
+
operation_transport_recovery?: number[];
|
|
17
|
+
authorization_binding: number;
|
|
18
|
+
semantic_capabilities: number;
|
|
19
|
+
/** Required only by operations that can durably mutate authority state. */
|
|
20
|
+
durable_mutation?: number;
|
|
21
|
+
}
|
|
22
|
+
export interface ConnectContractSupport {
|
|
23
|
+
operation_transport: number[];
|
|
24
|
+
authorization_binding: number[];
|
|
25
|
+
semantic_capabilities: number[];
|
|
26
|
+
durable_mutation: number[];
|
|
27
|
+
}
|
|
28
|
+
export declare const CONNECT_CONTRACT_SUPPORT: ConnectContractSupport;
|
|
29
|
+
/**
|
|
30
|
+
* Produce the exact contract ceiling signed during authorization. `sync` is
|
|
31
|
+
* conservatively mutation-capable because its action is selected per request.
|
|
32
|
+
*/
|
|
33
|
+
export declare function authorizationContractRequirements(operations: readonly CollectionOperation[], files?: ApplicationFileRequest, operationTransportRecovery?: readonly OperationTransportProtocolVersion[], semanticCapabilityContractVersion?: 1 | 2): ConnectContractRequirements;
|
|
34
|
+
export declare function supportsContractRequirements(required: ConnectContractRequirements, supported: ConnectContractSupport, durableMutation: boolean): boolean;
|
|
35
|
+
export declare function isSupportedOperationTransport(version: number): version is OperationTransportProtocolVersion;
|
|
36
|
+
export declare function isSupportedAuthorizationBinding(version: number): version is AuthorizationBindingProtocolVersion;
|
|
37
|
+
export declare function permitsOperationTransport(contracts: ConnectContractRequirements, version: number, recoveryOnly?: boolean): boolean;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { APPLICATION_CAPABILITY_CONTRACT_VERSION } from "./capabilities.js";
|
|
2
|
+
import { isMutatingOperation } from "./operations.js";
|
|
3
|
+
export const OPERATION_TRANSPORT_PROTOCOL_VERSION = 3;
|
|
4
|
+
export const LEGACY_OPERATION_TRANSPORT_PROTOCOL_VERSION = 2;
|
|
5
|
+
export const SUPPORTED_OPERATION_TRANSPORT_PROTOCOL_VERSIONS = [
|
|
6
|
+
OPERATION_TRANSPORT_PROTOCOL_VERSION,
|
|
7
|
+
LEGACY_OPERATION_TRANSPORT_PROTOCOL_VERSION
|
|
8
|
+
];
|
|
9
|
+
export const AUTHORIZATION_BINDING_PROTOCOL_VERSION = 5;
|
|
10
|
+
export const LEGACY_AUTHORIZATION_BINDING_PROTOCOL_VERSION = 4;
|
|
11
|
+
export const SUPPORTED_AUTHORIZATION_BINDING_PROTOCOL_VERSIONS = [
|
|
12
|
+
AUTHORIZATION_BINDING_PROTOCOL_VERSION,
|
|
13
|
+
LEGACY_AUTHORIZATION_BINDING_PROTOCOL_VERSION
|
|
14
|
+
];
|
|
15
|
+
export const SEMANTIC_CAPABILITY_CONTRACT_VERSION = APPLICATION_CAPABILITY_CONTRACT_VERSION;
|
|
16
|
+
export const DURABLE_MUTATION_CONTRACT_VERSION = 1;
|
|
17
|
+
export const CONNECT_CONTRACT_SUPPORT = {
|
|
18
|
+
operation_transport: [...SUPPORTED_OPERATION_TRANSPORT_PROTOCOL_VERSIONS],
|
|
19
|
+
authorization_binding: [...SUPPORTED_AUTHORIZATION_BINDING_PROTOCOL_VERSIONS],
|
|
20
|
+
semantic_capabilities: [SEMANTIC_CAPABILITY_CONTRACT_VERSION, 1],
|
|
21
|
+
durable_mutation: [DURABLE_MUTATION_CONTRACT_VERSION]
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Produce the exact contract ceiling signed during authorization. `sync` is
|
|
25
|
+
* conservatively mutation-capable because its action is selected per request.
|
|
26
|
+
*/
|
|
27
|
+
export function authorizationContractRequirements(operations, files, operationTransportRecovery = [], semanticCapabilityContractVersion = APPLICATION_CAPABILITY_CONTRACT_VERSION) {
|
|
28
|
+
const requirements = {
|
|
29
|
+
operation_transport: OPERATION_TRANSPORT_PROTOCOL_VERSION,
|
|
30
|
+
authorization_binding: AUTHORIZATION_BINDING_PROTOCOL_VERSION,
|
|
31
|
+
semantic_capabilities: semanticCapabilityContractVersion
|
|
32
|
+
};
|
|
33
|
+
const recovery = [...new Set(operationTransportRecovery)]
|
|
34
|
+
.filter((version) => version !== OPERATION_TRANSPORT_PROTOCOL_VERSION)
|
|
35
|
+
.sort((left, right) => right - left);
|
|
36
|
+
if (recovery.length > 0)
|
|
37
|
+
requirements.operation_transport_recovery = recovery;
|
|
38
|
+
if (operations.some((operation) => isMutatingOperation(operation, { action: "mutate" }))
|
|
39
|
+
|| files?.actions.some((action) => action !== "list" && action !== "read") === true) {
|
|
40
|
+
requirements.durable_mutation = 1;
|
|
41
|
+
}
|
|
42
|
+
return requirements;
|
|
43
|
+
}
|
|
44
|
+
export function supportsContractRequirements(required, supported, durableMutation) {
|
|
45
|
+
return supported.operation_transport.includes(required.operation_transport)
|
|
46
|
+
&& (required.operation_transport_recovery ?? []).every((version) => supported.operation_transport.includes(version))
|
|
47
|
+
&& supported.authorization_binding.includes(required.authorization_binding)
|
|
48
|
+
&& supported.semantic_capabilities.includes(required.semantic_capabilities)
|
|
49
|
+
&& (!durableMutation
|
|
50
|
+
|| (required.durable_mutation !== undefined
|
|
51
|
+
&& supported.durable_mutation.includes(required.durable_mutation)));
|
|
52
|
+
}
|
|
53
|
+
export function isSupportedOperationTransport(version) {
|
|
54
|
+
return SUPPORTED_OPERATION_TRANSPORT_PROTOCOL_VERSIONS
|
|
55
|
+
.includes(version);
|
|
56
|
+
}
|
|
57
|
+
export function isSupportedAuthorizationBinding(version) {
|
|
58
|
+
return SUPPORTED_AUTHORIZATION_BINDING_PROTOCOL_VERSIONS
|
|
59
|
+
.includes(version);
|
|
60
|
+
}
|
|
61
|
+
export function permitsOperationTransport(contracts, version, recoveryOnly = false) {
|
|
62
|
+
return contracts.operation_transport === version
|
|
63
|
+
|| (recoveryOnly
|
|
64
|
+
&& (contracts.operation_transport_recovery ?? []).includes(version));
|
|
65
|
+
}
|