@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.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/dist/application-authorization.d.ts +32 -0
  3. package/dist/application-authorization.js +207 -0
  4. package/dist/capabilities.d.ts +73 -0
  5. package/dist/capabilities.js +92 -0
  6. package/dist/collection-setup.d.ts +90 -0
  7. package/dist/collection-setup.js +1 -0
  8. package/dist/compatibility.d.ts +37 -0
  9. package/dist/compatibility.js +65 -0
  10. package/dist/connect-problems.generated.d.ts +1388 -0
  11. package/dist/connect-problems.generated.js +574 -0
  12. package/dist/files.d.ts +269 -0
  13. package/dist/files.js +426 -0
  14. package/dist/index.d.ts +878 -0
  15. package/dist/index.js +94 -0
  16. package/dist/manifest.d.ts +62 -0
  17. package/dist/manifest.js +527 -0
  18. package/dist/mutation-fingerprint.d.ts +6 -0
  19. package/dist/mutation-fingerprint.js +116 -0
  20. package/dist/operations.d.ts +15 -0
  21. package/dist/operations.js +194 -0
  22. package/dist/type-packs.d.ts +133 -0
  23. package/dist/type-packs.js +30 -0
  24. package/package.json +42 -0
  25. package/schemas/application-capability-catalog.v1.json +40 -0
  26. package/schemas/application-capability-catalog.v2.json +68 -0
  27. package/schemas/catalog.json +17 -0
  28. package/schemas/connect-problem.v1.schema.json +5132 -0
  29. package/schemas/connect-problems.v1.catalog.json +351 -0
  30. package/schemas/connect-protocol.v1.schema.json +800 -0
  31. package/schemas/data-contract.schema.json +163 -0
  32. package/schemas/encrypted-relay.v1.schema.json +63 -0
  33. package/schemas/encrypted-relay.v2.schema.json +63 -0
  34. package/schemas/encrypted-relay.v3.schema.json +64 -0
  35. package/schemas/files.v1.schema.json +507 -0
  36. package/schemas/interop/v0.1/action-cancellation.schema.json +6 -0
  37. package/schemas/interop/v0.1/action-invocation.schema.json +6 -0
  38. package/schemas/interop/v0.1/action-outcome.schema.json +6 -0
  39. package/schemas/interop/v0.1/action-provider-declaration.schema.json +6 -0
  40. package/schemas/interop/v0.1/action-request.schema.json +6 -0
  41. package/schemas/interop/v0.1/conformance-claim.schema.json +6 -0
  42. package/schemas/interop/v0.1/event-source-declaration.schema.json +6 -0
  43. package/schemas/interop/v0.1/event.schema.json +6 -0
  44. package/schemas/interop/v0.1/profile.schema.json +456 -0
  45. package/schemas/mdbase-app.legacy-v1.schema.json +405 -0
  46. package/schemas/mdbase-app.schema.json +381 -0
  47. package/schemas/mdbase-query.v0.3.schema.json +171 -0
  48. package/schemas/notification-webhook.v1.schema.json +56 -0
  49. package/schemas/operation-catalog.v1.json +42 -0
  50. package/schemas/operation-mutation-classification.v1.json +537 -0
  51. 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
+ }