@mdbase-dev/connect-protocol 0.1.0-beta.93 → 0.1.0-beta.95
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/dist/application-authorization.d.ts +2 -2
- package/dist/capabilities.d.ts +34 -15
- package/dist/capabilities.js +77 -37
- package/dist/compatibility.d.ts +3 -3
- package/dist/compatibility.js +5 -4
- package/dist/connect-problems.generated.d.ts +11 -0
- package/dist/connect-problems.generated.js +1 -0
- package/dist/files.d.ts +14 -0
- package/dist/files.js +9 -0
- package/dist/index.d.ts +37 -12
- package/dist/index.js +2 -0
- package/dist/manifest.d.ts +18 -1
- package/dist/manifest.js +70 -11
- package/package.json +2 -2
- package/schemas/application-capability-catalog.v1.json +40 -0
- package/schemas/application-capability-catalog.v2.json +68 -0
- package/schemas/connect-problem.v1.schema.json +40 -0
- package/schemas/connect-problems.v1.catalog.json +1 -0
- package/schemas/connect-protocol.v1.schema.json +1 -0
- package/schemas/mdbase-app.legacy-v1.schema.json +405 -0
- package/schemas/mdbase-app.schema.json +23 -47
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ApplicationFileRequest } from "./files.js";
|
|
2
2
|
import { type CollectionOperation } from "./operations.js";
|
|
3
3
|
import type { AuthorizationBindingProtocolVersion, ConnectContractRequirements } from "./compatibility.js";
|
|
4
4
|
export type ApplicationAuthorizationFlow = "authorization_code" | "device_code";
|
|
@@ -21,7 +21,7 @@ export interface ApplicationAuthorizationBinding {
|
|
|
21
21
|
code_challenge: string;
|
|
22
22
|
contracts: ConnectContractRequirements;
|
|
23
23
|
requested_operations: CollectionOperation[];
|
|
24
|
-
requested_files?:
|
|
24
|
+
requested_files?: ApplicationFileRequest;
|
|
25
25
|
collection_id?: string;
|
|
26
26
|
}
|
|
27
27
|
export interface ApplicationAuthorizationProof {
|
package/dist/capabilities.d.ts
CHANGED
|
@@ -1,10 +1,29 @@
|
|
|
1
1
|
import type { CollectionOperation } from "./operations.js";
|
|
2
|
-
export declare const APPLICATION_CAPABILITY_CONTRACT_VERSION:
|
|
3
|
-
/**
|
|
4
|
-
* Stable application-facing abilities. Applications declare intent in these
|
|
5
|
-
* terms; Connect alone translates them into wire-level collection operations.
|
|
6
|
-
*/
|
|
2
|
+
export declare const APPLICATION_CAPABILITY_CONTRACT_VERSION: 2;
|
|
7
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<{
|
|
8
27
|
readonly "collection.inspect": readonly ["describe"];
|
|
9
28
|
readonly "records.watch": readonly ["changes"];
|
|
10
29
|
readonly "records.read": readonly ["read"];
|
|
@@ -39,14 +58,14 @@ export declare const APPLICATION_CAPABILITY_DEFINITIONS: {
|
|
|
39
58
|
readonly "files.replace": readonly [];
|
|
40
59
|
readonly "files.move": readonly [];
|
|
41
60
|
readonly "files.delete": readonly [];
|
|
42
|
-
}
|
|
43
|
-
export type
|
|
44
|
-
export interface
|
|
45
|
-
contract_version: typeof
|
|
46
|
-
required:
|
|
47
|
-
optional?:
|
|
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[];
|
|
48
67
|
}
|
|
49
|
-
export declare function
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
export declare function
|
|
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];
|
|
71
|
+
export declare function permitsFreshApplicationAuthorization(version: number): boolean;
|
package/dist/capabilities.js
CHANGED
|
@@ -1,44 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* Stable application-facing abilities. Applications declare intent in these
|
|
4
|
-
* terms; Connect alone translates them into wire-level collection operations.
|
|
5
|
-
*/
|
|
1
|
+
// Generated by scripts/generate-application-capabilities.mjs. Do not edit directly.
|
|
2
|
+
export const APPLICATION_CAPABILITY_CONTRACT_VERSION = 2;
|
|
6
3
|
export const APPLICATION_CAPABILITY_DEFINITIONS = {
|
|
7
|
-
"collection.
|
|
8
|
-
"records.watch": ["changes"],
|
|
9
|
-
"records.read": ["read"],
|
|
10
|
-
"records.query": ["query"],
|
|
11
|
-
"records.validate": ["validate"],
|
|
4
|
+
"collection.read": ["describe", "changes", "read", "query", "list_views", "execute_view", "read_view_source", "validate", "read_type"],
|
|
12
5
|
"records.create": ["create"],
|
|
13
|
-
"records.
|
|
6
|
+
"records.edit": ["update", "rename"],
|
|
14
7
|
"records.delete": ["delete"],
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"views.source.create": ["create_view_source"],
|
|
20
|
-
"views.source.update": ["update_view_source"],
|
|
21
|
-
"views.source.delete": ["delete_view_source"],
|
|
22
|
-
"definitions.contracts.current": [],
|
|
23
|
-
"definitions.read": ["read_type"],
|
|
24
|
-
"definitions.create": ["create_type"],
|
|
25
|
-
"definitions.update": ["update_type"],
|
|
26
|
-
"definitions.type-pack.inspect": ["assess_type_pack"],
|
|
27
|
-
"definitions.type-pack.apply": ["assess_type_pack", "apply_type_pack"],
|
|
28
|
-
"collection.setup.apply": ["assess_collection_setup", "apply_collection_setup"],
|
|
29
|
-
"timers.list": ["list_timers"],
|
|
30
|
-
"timers.put": ["put_timer"],
|
|
31
|
-
"timers.cancel": ["cancel_timer"],
|
|
32
|
-
"timers.reconcile": ["reconcile_timers"],
|
|
33
|
-
"sync.offline-replica": ["sync"],
|
|
34
|
-
"notifications.background-delivery": [],
|
|
35
|
-
"files.list": [],
|
|
36
|
-
"files.read": [],
|
|
37
|
-
"files.add": [],
|
|
38
|
-
"files.replace": [],
|
|
39
|
-
"files.move": [],
|
|
40
|
-
"files.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"]
|
|
41
12
|
};
|
|
13
|
+
export const APPLICATION_SETUP_OPERATIONS = [
|
|
14
|
+
"assess_collection_setup",
|
|
15
|
+
"apply_collection_setup"
|
|
16
|
+
];
|
|
42
17
|
export function operationsForApplicationCapabilities(requirements, options = {}) {
|
|
43
18
|
const capabilities = options.includeOptional === false
|
|
44
19
|
? requirements.required
|
|
@@ -48,3 +23,68 @@ export function operationsForApplicationCapabilities(requirements, options = {})
|
|
|
48
23
|
export function capabilityOperations(capability) {
|
|
49
24
|
return [...APPLICATION_CAPABILITY_DEFINITIONS[capability]];
|
|
50
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]);
|
|
88
|
+
export function permitsFreshApplicationAuthorization(version) {
|
|
89
|
+
return FRESH_APPLICATION_AUTHORIZATION_VERSIONS.some((supported) => supported === version);
|
|
90
|
+
}
|
package/dist/compatibility.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ApplicationFileRequest } from "./files.js";
|
|
2
2
|
import { type CollectionOperation } from "./operations.js";
|
|
3
3
|
export declare const OPERATION_TRANSPORT_PROTOCOL_VERSION: 3;
|
|
4
4
|
export declare const LEGACY_OPERATION_TRANSPORT_PROTOCOL_VERSION: 2;
|
|
@@ -8,7 +8,7 @@ export declare const AUTHORIZATION_BINDING_PROTOCOL_VERSION: 5;
|
|
|
8
8
|
export declare const LEGACY_AUTHORIZATION_BINDING_PROTOCOL_VERSION: 4;
|
|
9
9
|
export declare const SUPPORTED_AUTHORIZATION_BINDING_PROTOCOL_VERSIONS: readonly [5, 4];
|
|
10
10
|
export type AuthorizationBindingProtocolVersion = typeof SUPPORTED_AUTHORIZATION_BINDING_PROTOCOL_VERSIONS[number];
|
|
11
|
-
export declare const SEMANTIC_CAPABILITY_CONTRACT_VERSION:
|
|
11
|
+
export declare const SEMANTIC_CAPABILITY_CONTRACT_VERSION: 2;
|
|
12
12
|
export declare const DURABLE_MUTATION_CONTRACT_VERSION: 1;
|
|
13
13
|
export interface ConnectContractRequirements {
|
|
14
14
|
operation_transport: number;
|
|
@@ -30,7 +30,7 @@ export declare const CONNECT_CONTRACT_SUPPORT: ConnectContractSupport;
|
|
|
30
30
|
* Produce the exact contract ceiling signed during authorization. `sync` is
|
|
31
31
|
* conservatively mutation-capable because its action is selected per request.
|
|
32
32
|
*/
|
|
33
|
-
export declare function authorizationContractRequirements(operations: readonly CollectionOperation[], files?:
|
|
33
|
+
export declare function authorizationContractRequirements(operations: readonly CollectionOperation[], files?: ApplicationFileRequest, operationTransportRecovery?: readonly OperationTransportProtocolVersion[], semanticCapabilityContractVersion?: 1 | 2): ConnectContractRequirements;
|
|
34
34
|
export declare function supportsContractRequirements(required: ConnectContractRequirements, supported: ConnectContractSupport, durableMutation: boolean): boolean;
|
|
35
35
|
export declare function isSupportedOperationTransport(version: number): version is OperationTransportProtocolVersion;
|
|
36
36
|
export declare function isSupportedAuthorizationBinding(version: number): version is AuthorizationBindingProtocolVersion;
|
package/dist/compatibility.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { APPLICATION_CAPABILITY_CONTRACT_VERSION } from "./capabilities.js";
|
|
1
2
|
import { isMutatingOperation } from "./operations.js";
|
|
2
3
|
export const OPERATION_TRANSPORT_PROTOCOL_VERSION = 3;
|
|
3
4
|
export const LEGACY_OPERATION_TRANSPORT_PROTOCOL_VERSION = 2;
|
|
@@ -11,23 +12,23 @@ export const SUPPORTED_AUTHORIZATION_BINDING_PROTOCOL_VERSIONS = [
|
|
|
11
12
|
AUTHORIZATION_BINDING_PROTOCOL_VERSION,
|
|
12
13
|
LEGACY_AUTHORIZATION_BINDING_PROTOCOL_VERSION
|
|
13
14
|
];
|
|
14
|
-
export const SEMANTIC_CAPABILITY_CONTRACT_VERSION =
|
|
15
|
+
export const SEMANTIC_CAPABILITY_CONTRACT_VERSION = APPLICATION_CAPABILITY_CONTRACT_VERSION;
|
|
15
16
|
export const DURABLE_MUTATION_CONTRACT_VERSION = 1;
|
|
16
17
|
export const CONNECT_CONTRACT_SUPPORT = {
|
|
17
18
|
operation_transport: [...SUPPORTED_OPERATION_TRANSPORT_PROTOCOL_VERSIONS],
|
|
18
19
|
authorization_binding: [...SUPPORTED_AUTHORIZATION_BINDING_PROTOCOL_VERSIONS],
|
|
19
|
-
semantic_capabilities: [SEMANTIC_CAPABILITY_CONTRACT_VERSION],
|
|
20
|
+
semantic_capabilities: [SEMANTIC_CAPABILITY_CONTRACT_VERSION, 1],
|
|
20
21
|
durable_mutation: [DURABLE_MUTATION_CONTRACT_VERSION]
|
|
21
22
|
};
|
|
22
23
|
/**
|
|
23
24
|
* Produce the exact contract ceiling signed during authorization. `sync` is
|
|
24
25
|
* conservatively mutation-capable because its action is selected per request.
|
|
25
26
|
*/
|
|
26
|
-
export function authorizationContractRequirements(operations, files, operationTransportRecovery = []) {
|
|
27
|
+
export function authorizationContractRequirements(operations, files, operationTransportRecovery = [], semanticCapabilityContractVersion = APPLICATION_CAPABILITY_CONTRACT_VERSION) {
|
|
27
28
|
const requirements = {
|
|
28
29
|
operation_transport: OPERATION_TRANSPORT_PROTOCOL_VERSION,
|
|
29
30
|
authorization_binding: AUTHORIZATION_BINDING_PROTOCOL_VERSION,
|
|
30
|
-
semantic_capabilities:
|
|
31
|
+
semantic_capabilities: semanticCapabilityContractVersion
|
|
31
32
|
};
|
|
32
33
|
const recovery = [...new Set(operationTransportRecovery)]
|
|
33
34
|
.filter((version) => version !== OPERATION_TRANSPORT_PROTOCOL_VERSION)
|
|
@@ -264,6 +264,10 @@ export declare const CONNECT_PROBLEM_CATALOG: {
|
|
|
264
264
|
readonly category: "integrity";
|
|
265
265
|
readonly recovery: "contact_support";
|
|
266
266
|
};
|
|
267
|
+
readonly legacy_scope_reauthorization_required: {
|
|
268
|
+
readonly category: "authorization";
|
|
269
|
+
readonly recovery: "reauthorize";
|
|
270
|
+
};
|
|
267
271
|
readonly managed_fcm_not_declared: {
|
|
268
272
|
readonly category: "compatibility";
|
|
269
273
|
readonly recovery: "fix_request";
|
|
@@ -595,6 +599,7 @@ export interface ConnectProblemDetailsByCode {
|
|
|
595
599
|
"invalid_read_cursor": undefined;
|
|
596
600
|
"invalid_request": undefined;
|
|
597
601
|
"invalid_token_response": undefined;
|
|
602
|
+
"legacy_scope_reauthorization_required": undefined;
|
|
598
603
|
"managed_fcm_not_declared": undefined;
|
|
599
604
|
"manifest_load_failed": undefined;
|
|
600
605
|
"manifest_required": undefined;
|
|
@@ -1058,6 +1063,12 @@ export interface ConnectProblemByCode {
|
|
|
1058
1063
|
recovery: "contact_support";
|
|
1059
1064
|
details?: never;
|
|
1060
1065
|
};
|
|
1066
|
+
"legacy_scope_reauthorization_required": ConnectProblemBase & {
|
|
1067
|
+
code: "legacy_scope_reauthorization_required";
|
|
1068
|
+
category: "authorization";
|
|
1069
|
+
recovery: "reauthorize";
|
|
1070
|
+
details?: never;
|
|
1071
|
+
};
|
|
1061
1072
|
"managed_fcm_not_declared": ConnectProblemBase & {
|
|
1062
1073
|
code: "managed_fcm_not_declared";
|
|
1063
1074
|
category: "compatibility";
|
|
@@ -67,6 +67,7 @@ export const CONNECT_PROBLEM_CATALOG = {
|
|
|
67
67
|
"invalid_read_cursor": { category: "validation", recovery: "fix_request" },
|
|
68
68
|
"invalid_request": { category: "validation", recovery: "fix_request" },
|
|
69
69
|
"invalid_token_response": { category: "integrity", recovery: "contact_support" },
|
|
70
|
+
"legacy_scope_reauthorization_required": { category: "authorization", recovery: "reauthorize" },
|
|
70
71
|
"managed_fcm_not_declared": { category: "compatibility", recovery: "fix_request" },
|
|
71
72
|
"manifest_load_failed": { category: "availability", recovery: "retry" },
|
|
72
73
|
"manifest_required": { category: "validation", recovery: "fix_request" },
|
package/dist/files.d.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/** Frozen predecessor file declaration; never translate this into v2 intent. */
|
|
2
|
+
export interface LegacyApplicationFileRequirement {
|
|
3
|
+
actions: FileAction[];
|
|
4
|
+
scope: FileScope;
|
|
5
|
+
}
|
|
1
6
|
export declare const FILE_PROTOCOL_VERSION: 1;
|
|
2
7
|
export declare const FILE_TRANSFER_PROTOCOL_VERSION: 1;
|
|
3
8
|
export declare const DEFAULT_FILE_CHUNK_BYTES: number;
|
|
@@ -43,9 +48,18 @@ export type FileScope = {
|
|
|
43
48
|
};
|
|
44
49
|
/** File intent declared by an application before an authority is selected. */
|
|
45
50
|
export interface ApplicationFileRequirement {
|
|
51
|
+
required: FileAction[];
|
|
52
|
+
optional?: FileAction[];
|
|
53
|
+
scope: FileScope;
|
|
54
|
+
}
|
|
55
|
+
/** Exact file actions signed into an authorization request. */
|
|
56
|
+
export interface ApplicationFileRequest {
|
|
46
57
|
actions: FileAction[];
|
|
47
58
|
scope: FileScope;
|
|
48
59
|
}
|
|
60
|
+
export declare function applicationFileRequest(requirement: ApplicationFileRequirement, options?: {
|
|
61
|
+
includeOptional?: boolean;
|
|
62
|
+
}): ApplicationFileRequest;
|
|
49
63
|
export interface FileCapability {
|
|
50
64
|
kind: "files";
|
|
51
65
|
protocol_version: 1;
|
package/dist/files.js
CHANGED
|
@@ -21,6 +21,15 @@ const RELAY_FILE_MAGIC_BYTES = new Uint8Array([0x4d, 0x44, 0x42, 0x52]);
|
|
|
21
21
|
const RELAY_FILE_FRAME_VERSION = 1;
|
|
22
22
|
const RELAY_FILE_FRAME_FLAGS = 0;
|
|
23
23
|
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
24
|
+
export function applicationFileRequest(requirement, options = {}) {
|
|
25
|
+
return {
|
|
26
|
+
actions: [...new Set([
|
|
27
|
+
...requirement.required,
|
|
28
|
+
...(options.includeOptional === false ? [] : requirement.optional ?? [])
|
|
29
|
+
])],
|
|
30
|
+
scope: structuredClone(requirement.scope)
|
|
31
|
+
};
|
|
32
|
+
}
|
|
24
33
|
export class FileFrameError extends Error {
|
|
25
34
|
code;
|
|
26
35
|
constructor(code, message) {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ConnectProblem } from "./connect-problems.generated.js";
|
|
2
|
-
import type { ApplicationFileRequirement, CollectionFileDescriptor, FileCapability } from "./files.js";
|
|
2
|
+
import type { ApplicationFileRequirement, LegacyApplicationFileRequirement, CollectionFileDescriptor, FileCapability } from "./files.js";
|
|
3
3
|
import type { CollectionOperation } from "./operations.js";
|
|
4
|
-
import type { ApplicationCapabilityRequirements } from "./capabilities.js";
|
|
4
|
+
import type { ApplicationCapabilityRequirements, LegacyApplicationCapabilityRequirements } from "./capabilities.js";
|
|
5
5
|
import type { ContractRequirement, ContractSetupChoice, TypePackProvision } from "./type-packs.js";
|
|
6
6
|
import type { ConfigurationProvision, ConfigurationRequirement } from "./collection-setup.js";
|
|
7
7
|
import type { ApplicationAuthorizationProof } from "./application-authorization.js";
|
|
@@ -32,7 +32,8 @@ export declare const POLICY_FRESHNESS_LEASE_MINIMUM_CONNECTOR_VERSION: "0.1.0-be
|
|
|
32
32
|
export declare const HOSTED_PROVIDER_REQUIRED_CAPABILITIES: readonly ["durable-mutation-journal-v1", "durable-file-lifecycle-v1"];
|
|
33
33
|
export declare const HOSTED_CANDIDATE_B_ACTIVATION_CAPABILITY: "candidate-b-activation-v1";
|
|
34
34
|
export declare const HOSTED_PROVIDER_CAPABILITIES: readonly ["durable-mutation-journal-v1", "durable-file-lifecycle-v1", "mutation-replay-after-credential-retirement-v1", "candidate-b-activation-v1"];
|
|
35
|
-
export declare const
|
|
35
|
+
export declare const APPLICATION_DECLARATION_EVIDENCE_CAPABILITY: "application-declaration-evidence-v1";
|
|
36
|
+
export declare const RELAY_CAPABILITIES: readonly ["application-declaration-evidence-v1", "application-authorization-v4", "authorization-activation", "encrypted-relay", "policy-ack", "policy-freshness-lease-v1", "application-authorization-v5", "contract-setup-v1", "file-relay-v1", "protocol-usage-report-v1"];
|
|
36
37
|
export declare const AUTHORITY_PROOF_VERSION: 1;
|
|
37
38
|
export declare const AUTHORITY_PROOF_ALGORITHM: "P256-SHA256";
|
|
38
39
|
export declare const AUTHORITY_PROOF_DOMAIN: "mdbase-authority-request-proof-v1";
|
|
@@ -102,11 +103,15 @@ export interface ApplicationNotifications {
|
|
|
102
103
|
* `id` is stable presentation metadata rather than proof of a publisher. Each
|
|
103
104
|
* exact declaration is independently identified and authorized by Connect.
|
|
104
105
|
*/
|
|
106
|
+
export type CanonicalApplicationRequirements = Omit<ApplicationRequirements, "access" | "contracts"> & {
|
|
107
|
+
access: "full_collection";
|
|
108
|
+
contracts: [];
|
|
109
|
+
};
|
|
105
110
|
interface MdbaseAppManifestBase {
|
|
106
111
|
manifest_version: 1;
|
|
107
112
|
id: string;
|
|
108
113
|
name: string;
|
|
109
|
-
requirements
|
|
114
|
+
requirements: CanonicalApplicationRequirements;
|
|
110
115
|
provisions?: ApplicationProvisions;
|
|
111
116
|
notifications?: ApplicationNotifications;
|
|
112
117
|
}
|
|
@@ -164,13 +169,29 @@ export interface ApplicationRequirements {
|
|
|
164
169
|
configuration?: ConfigurationRequirement[];
|
|
165
170
|
/** Versioned semantic intent compiled by Connect into exact operations. */
|
|
166
171
|
capabilities?: ApplicationCapabilityRequirements;
|
|
167
|
-
/**
|
|
172
|
+
/**
|
|
173
|
+
* Collection boundary request. New declarations must explicitly use
|
|
174
|
+
* `full_collection`; omitted and `contract` remain parseable only for legacy
|
|
175
|
+
* diagnosis and reauthorization.
|
|
176
|
+
*/
|
|
168
177
|
access?: "contract" | "full_collection";
|
|
169
178
|
/** Restrict authorization to durable provider-backed collections. */
|
|
170
179
|
collection_kind?: "hosted";
|
|
171
180
|
/** First-class non-Markdown file access requested independently of records. */
|
|
172
181
|
files?: ApplicationFileRequirement;
|
|
173
182
|
}
|
|
183
|
+
export type LegacyApplicationRequirements = Omit<ApplicationRequirements, "capabilities" | "files" | "access"> & {
|
|
184
|
+
access: "full_collection";
|
|
185
|
+
capabilities?: LegacyApplicationCapabilityRequirements;
|
|
186
|
+
files?: LegacyApplicationFileRequirement;
|
|
187
|
+
};
|
|
188
|
+
export type LegacyMdbaseWebAppManifest = Omit<MdbaseWebAppManifest, "requirements"> & {
|
|
189
|
+
requirements: LegacyApplicationRequirements;
|
|
190
|
+
};
|
|
191
|
+
export type LegacyMdbasePortableAppManifest = Omit<MdbasePortableAppManifest, "requirements"> & {
|
|
192
|
+
requirements: LegacyApplicationRequirements;
|
|
193
|
+
};
|
|
194
|
+
export type LegacyMdbaseAppManifest = LegacyMdbaseWebAppManifest | LegacyMdbasePortableAppManifest;
|
|
174
195
|
export interface ApplicationProvisions {
|
|
175
196
|
type_packs: TypePackProvision[];
|
|
176
197
|
/** Narrow semantic changes that may satisfy configuration requirements. */
|
|
@@ -178,11 +199,12 @@ export interface ApplicationProvisions {
|
|
|
178
199
|
}
|
|
179
200
|
export interface GrantScope {
|
|
180
201
|
/**
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
202
|
+
* Legacy semantic scope payload retained for wire/storage compatibility.
|
|
203
|
+
* Canonical application grants use an empty array; contracts remain available
|
|
204
|
+
* through collection resources and operation-level semantic selectors.
|
|
184
205
|
*/
|
|
185
206
|
contracts: CollectionContractDescriptor[];
|
|
207
|
+
/** New authority must be `full_collection`; `contract` is legacy-only. */
|
|
186
208
|
access: "contract" | "full_collection";
|
|
187
209
|
}
|
|
188
210
|
export interface RelayOperationRequest {
|
|
@@ -225,6 +247,8 @@ export type MdbaseOperationResponse<Result = unknown> = {
|
|
|
225
247
|
problem: ConnectProblem;
|
|
226
248
|
};
|
|
227
249
|
export interface GrantPolicy {
|
|
250
|
+
/** Complete normalized JSON evidence; never independent operation authority. */
|
|
251
|
+
application_declaration?: unknown;
|
|
228
252
|
id: string;
|
|
229
253
|
application_id: string;
|
|
230
254
|
/** Stable declaration identity bound into the exact application authorization. */
|
|
@@ -519,10 +543,11 @@ export interface RelayPolicySnapshot {
|
|
|
519
543
|
protocol_version: 1;
|
|
520
544
|
request_id: string;
|
|
521
545
|
revision: string;
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
546
|
+
/** Lease fields are absent on legacy N-1 grants-only snapshots. */
|
|
547
|
+
connector_id?: string;
|
|
548
|
+
sequence?: number;
|
|
549
|
+
lease_issued_at_ms?: number;
|
|
550
|
+
lease_expires_at_ms?: number;
|
|
526
551
|
grants: GrantPolicy[];
|
|
527
552
|
}
|
|
528
553
|
export interface RelayHello {
|
package/dist/index.js
CHANGED
|
@@ -37,7 +37,9 @@ export const HOSTED_PROVIDER_CAPABILITIES = [
|
|
|
37
37
|
"mutation-replay-after-credential-retirement-v1",
|
|
38
38
|
HOSTED_CANDIDATE_B_ACTIVATION_CAPABILITY
|
|
39
39
|
];
|
|
40
|
+
export const APPLICATION_DECLARATION_EVIDENCE_CAPABILITY = "application-declaration-evidence-v1";
|
|
40
41
|
export const RELAY_CAPABILITIES = [
|
|
42
|
+
APPLICATION_DECLARATION_EVIDENCE_CAPABILITY,
|
|
41
43
|
...RELAY_REQUIRED_CAPABILITIES,
|
|
42
44
|
POLICY_FRESHNESS_LEASE_CAPABILITY,
|
|
43
45
|
"application-authorization-v5",
|
package/dist/manifest.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { LegacyMdbaseAppManifest } from "./index.js";
|
|
2
|
+
export type { LegacyApplicationFileRequirement, LegacyApplicationRequirements, LegacyMdbaseWebAppManifest, LegacyMdbasePortableAppManifest, LegacyMdbaseAppManifest } from "./index.js";
|
|
1
3
|
import { type ApplicationNotifications, type ApplicationProvisions, type ApplicationRequirements, type MdbasePortableAppManifest, type MdbaseWebAppManifest } from "./index.js";
|
|
2
4
|
export declare const APPLICATION_MANIFEST_MAX_BYTES = 524288;
|
|
3
5
|
export interface ManifestValidationIssue {
|
|
@@ -27,6 +29,18 @@ interface ValidatedManifestFields {
|
|
|
27
29
|
export type ValidatedWebAppManifest = Omit<MdbaseWebAppManifest, keyof ValidatedManifestFields> & ValidatedManifestFields;
|
|
28
30
|
export type ValidatedPortableAppManifest = Omit<MdbasePortableAppManifest, keyof ValidatedManifestFields> & ValidatedManifestFields;
|
|
29
31
|
export type ValidatedAppManifest = ValidatedWebAppManifest | ValidatedPortableAppManifest;
|
|
32
|
+
export type ValidatedLegacyAppManifest = LegacyMdbaseAppManifest & {
|
|
33
|
+
provisions: ApplicationProvisions;
|
|
34
|
+
notifications: ApplicationNotifications;
|
|
35
|
+
};
|
|
36
|
+
/** The discriminator describes the original declaration, not a translation. */
|
|
37
|
+
export type VersionedAppManifest = {
|
|
38
|
+
contractVersion: 1;
|
|
39
|
+
manifest: ValidatedLegacyAppManifest;
|
|
40
|
+
} | {
|
|
41
|
+
contractVersion: 2;
|
|
42
|
+
manifest: ValidatedAppManifest;
|
|
43
|
+
};
|
|
30
44
|
export declare class AppManifestValidationError extends Error {
|
|
31
45
|
readonly issues: ManifestValidationIssue[];
|
|
32
46
|
constructor(issues: ManifestValidationIssue[]);
|
|
@@ -39,7 +53,10 @@ export declare class AppManifestValidationError extends Error {
|
|
|
39
53
|
* rules here; consumers must not maintain parallel manifest validators.
|
|
40
54
|
*/
|
|
41
55
|
export declare function validateAppManifest(value: unknown, options?: ManifestValidationOptions): ManifestValidationResult;
|
|
56
|
+
/** Explicit opt-in to predecessor declarations; omission defaults to v1. */
|
|
57
|
+
export declare function validateVersionedAppManifest(value: unknown, options?: ManifestValidationOptions): ManifestValidationResult;
|
|
42
58
|
/** Validate, normalize optional sections, and return an isolated declaration. */
|
|
43
59
|
export declare function parseAppManifest(value: unknown, options?: ManifestValidationOptions): ValidatedAppManifest;
|
|
60
|
+
/** Validate and clone without changing capability IDs, file aliases, or version. */
|
|
61
|
+
export declare function parseVersionedAppManifest(value: unknown, options?: ManifestValidationOptions): VersionedAppManifest;
|
|
44
62
|
export declare function formatManifestValidationIssues(issues: readonly ManifestValidationIssue[]): string;
|
|
45
|
-
export {};
|
package/dist/manifest.js
CHANGED
|
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
3
3
|
import addFormatsModule from "ajv-formats";
|
|
4
4
|
import appManifestSchema from "../schemas/mdbase-app.schema.json" with { type: "json" };
|
|
5
|
+
import legacyAppManifestSchema from "../schemas/mdbase-app.legacy-v1.schema.json" with { type: "json" };
|
|
5
6
|
import { isNativeRedirectUri } from "./index.js";
|
|
6
7
|
export const APPLICATION_MANIFEST_MAX_BYTES = 524_288;
|
|
7
8
|
export class AppManifestValidationError extends Error {
|
|
@@ -22,6 +23,8 @@ const addFormats = addFormatsModule;
|
|
|
22
23
|
addFormats(ajv);
|
|
23
24
|
ajv.addSchema(appManifestSchema);
|
|
24
25
|
const appManifestValidator = requiredValidator(String(appManifestSchema.$id));
|
|
26
|
+
ajv.addSchema(legacyAppManifestSchema);
|
|
27
|
+
const legacyAppManifestValidator = requiredValidator(String(legacyAppManifestSchema.$id));
|
|
25
28
|
/**
|
|
26
29
|
* Validate the complete bundled application declaration accepted by Connect.
|
|
27
30
|
*
|
|
@@ -30,16 +33,35 @@ const appManifestValidator = requiredValidator(String(appManifestSchema.$id));
|
|
|
30
33
|
* rules here; consumers must not maintain parallel manifest validators.
|
|
31
34
|
*/
|
|
32
35
|
export function validateAppManifest(value, options = {}) {
|
|
36
|
+
return validateManifest(value, options, false);
|
|
37
|
+
}
|
|
38
|
+
/** Explicit opt-in to predecessor declarations; omission defaults to v1. */
|
|
39
|
+
export function validateVersionedAppManifest(value, options = {}) {
|
|
40
|
+
return validateManifest(value, options, true);
|
|
41
|
+
}
|
|
42
|
+
function declaredContractVersion(value) {
|
|
43
|
+
const requirements = asObject(asObject(value).requirements);
|
|
44
|
+
return requirements.capabilities === undefined
|
|
45
|
+
? 1
|
|
46
|
+
: asObject(requirements.capabilities).contract_version;
|
|
47
|
+
}
|
|
48
|
+
function validateManifest(value, options, versioned) {
|
|
33
49
|
const sizeIssue = validateSerializedSize(value, options.maxBytes ?? APPLICATION_MANIFEST_MAX_BYTES);
|
|
34
50
|
if (sizeIssue)
|
|
35
51
|
return invalid([sizeIssue]);
|
|
52
|
+
const version = versioned ? declaredContractVersion(value) : 2;
|
|
53
|
+
if (version !== 1 && version !== 2) {
|
|
54
|
+
return invalid([issue("/requirements/capabilities/contract_version", "contractVersion", "must declare supported contract version 1 or 2")]);
|
|
55
|
+
}
|
|
36
56
|
const candidate = options.allowLocal ? localManifestSchemaCandidate(value) : value;
|
|
37
|
-
const schemaResult = validationResult(appManifestValidator, candidate);
|
|
57
|
+
const schemaResult = validationResult(version === 1 ? legacyAppManifestValidator : appManifestValidator, candidate);
|
|
38
58
|
if (!schemaResult.valid)
|
|
39
59
|
return schemaResult;
|
|
40
60
|
const issues = [
|
|
41
61
|
...validateManifestOrigins(value, options.allowLocal === true),
|
|
42
|
-
...
|
|
62
|
+
...(version === 1
|
|
63
|
+
? validateLegacyCapabilityRequirements(value)
|
|
64
|
+
: validateCapabilityRequirements(value)),
|
|
43
65
|
...validateProvisionRequirements(value),
|
|
44
66
|
...validateConfigurationSetup(value)
|
|
45
67
|
];
|
|
@@ -50,11 +72,22 @@ export function parseAppManifest(value, options = {}) {
|
|
|
50
72
|
const result = validateAppManifest(value, options);
|
|
51
73
|
if (!result.valid)
|
|
52
74
|
throw new AppManifestValidationError(result.issues);
|
|
75
|
+
return normalizeManifest(value);
|
|
76
|
+
}
|
|
77
|
+
/** Validate and clone without changing capability IDs, file aliases, or version. */
|
|
78
|
+
export function parseVersionedAppManifest(value, options = {}) {
|
|
79
|
+
const result = validateVersionedAppManifest(value, options);
|
|
80
|
+
if (!result.valid)
|
|
81
|
+
throw new AppManifestValidationError(result.issues);
|
|
82
|
+
return declaredContractVersion(value) === 1
|
|
83
|
+
? { contractVersion: 1, manifest: normalizeManifest(value) }
|
|
84
|
+
: { contractVersion: 2, manifest: normalizeManifest(value) };
|
|
85
|
+
}
|
|
86
|
+
function normalizeManifest(value) {
|
|
53
87
|
const manifest = structuredClone(value);
|
|
54
88
|
return {
|
|
55
89
|
...manifest,
|
|
56
90
|
requirements: {
|
|
57
|
-
contracts: [],
|
|
58
91
|
configuration: [],
|
|
59
92
|
...manifest.requirements
|
|
60
93
|
},
|
|
@@ -355,27 +388,53 @@ function validateCapabilityRequirements(value) {
|
|
|
355
388
|
const manifest = asObject(value);
|
|
356
389
|
const requirements = asObject(manifest.requirements);
|
|
357
390
|
const capabilities = asObject(requirements.capabilities);
|
|
358
|
-
|
|
359
|
-
|
|
391
|
+
const issues = [];
|
|
392
|
+
if (requirements.access !== "full_collection") {
|
|
393
|
+
issues.push(issue("/requirements/access", "collectionAccess", "must explicitly request full_collection; legacy or omitted access is not widened"));
|
|
394
|
+
}
|
|
360
395
|
const required = Array.isArray(capabilities.required)
|
|
361
396
|
? capabilities.required.map(String)
|
|
362
397
|
: [];
|
|
363
398
|
const optional = Array.isArray(capabilities.optional)
|
|
364
399
|
? capabilities.optional.map(String)
|
|
365
400
|
: [];
|
|
401
|
+
const overlap = optional.find((capability) => required.includes(capability));
|
|
402
|
+
if (overlap) {
|
|
403
|
+
issues.push(issue("/requirements/capabilities/optional", "disjoint", `must not repeat required capability ${overlap}`));
|
|
404
|
+
}
|
|
405
|
+
const files = asObject(requirements.files);
|
|
406
|
+
const requiredFiles = Array.isArray(files.required) ? files.required.map(String) : [];
|
|
407
|
+
const optionalFiles = Array.isArray(files.optional) ? files.optional.map(String) : [];
|
|
408
|
+
const fileOverlap = optionalFiles.find((action) => requiredFiles.includes(action));
|
|
409
|
+
if (fileOverlap) {
|
|
410
|
+
issues.push(issue("/requirements/files/optional", "disjoint", `must not repeat required file action ${fileOverlap}`));
|
|
411
|
+
}
|
|
412
|
+
return issues;
|
|
413
|
+
}
|
|
414
|
+
// Frozen cross-field rules from c2596a6e; notably, absent capabilities skip
|
|
415
|
+
// alias/setup checks rather than inventing or widening a declaration.
|
|
416
|
+
function validateLegacyCapabilityRequirements(value) {
|
|
417
|
+
const manifest = asObject(value);
|
|
418
|
+
const requirements = asObject(manifest.requirements);
|
|
419
|
+
const capabilities = asObject(requirements.capabilities);
|
|
366
420
|
const issues = [];
|
|
421
|
+
if (requirements.access !== "full_collection") {
|
|
422
|
+
issues.push(issue("/requirements/access", "collectionAccess", "must explicitly request full_collection; legacy or omitted access is not widened"));
|
|
423
|
+
}
|
|
424
|
+
if (Object.keys(capabilities).length === 0)
|
|
425
|
+
return issues;
|
|
426
|
+
const required = Array.isArray(capabilities.required)
|
|
427
|
+
? capabilities.required.map(String)
|
|
428
|
+
: [];
|
|
429
|
+
const optional = Array.isArray(capabilities.optional)
|
|
430
|
+
? capabilities.optional.map(String)
|
|
431
|
+
: [];
|
|
367
432
|
const overlap = optional.find((capability) => required.includes(capability));
|
|
368
433
|
if (overlap) {
|
|
369
434
|
issues.push(issue("/requirements/capabilities/optional", "disjoint", `must not repeat required capability ${overlap}`));
|
|
370
435
|
}
|
|
371
436
|
const declared = new Set([...required, ...optional]);
|
|
372
437
|
const provisions = asObject(manifest.provisions);
|
|
373
|
-
if (Array.isArray(requirements.contracts)
|
|
374
|
-
&& requirements.contracts.length > 0
|
|
375
|
-
&& requirements.access !== "full_collection"
|
|
376
|
-
&& !required.includes("definitions.contracts.current")) {
|
|
377
|
-
issues.push(issue("/requirements/capabilities/required", "contractCapability", "must require definitions.contracts.current for contract-scoped requirements"));
|
|
378
|
-
}
|
|
379
438
|
if (declared.has("definitions.type-pack.apply")) {
|
|
380
439
|
if (requirements.access !== "full_collection") {
|
|
381
440
|
issues.push(issue("/requirements/access", "typePackAccess", "must be full_collection for definitions.type-pack.apply"));
|