@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.
@@ -1,4 +1,4 @@
1
- import type { ApplicationFileRequirement } from "./files.js";
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?: ApplicationFileRequirement;
24
+ requested_files?: ApplicationFileRequest;
25
25
  collection_id?: string;
26
26
  }
27
27
  export interface ApplicationAuthorizationProof {
@@ -1,10 +1,29 @@
1
1
  import type { CollectionOperation } from "./operations.js";
2
- export declare const APPLICATION_CAPABILITY_CONTRACT_VERSION: 1;
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 ApplicationCapabilityId = keyof typeof APPLICATION_CAPABILITY_DEFINITIONS;
44
- export interface ApplicationCapabilityRequirements {
45
- contract_version: typeof APPLICATION_CAPABILITY_CONTRACT_VERSION;
46
- required: ApplicationCapabilityId[];
47
- optional?: ApplicationCapabilityId[];
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 operationsForApplicationCapabilities(requirements: ApplicationCapabilityRequirements, options?: {
50
- includeOptional?: boolean;
51
- }): CollectionOperation[];
52
- export declare function capabilityOperations(capability: ApplicationCapabilityId): CollectionOperation[];
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;
@@ -1,44 +1,19 @@
1
- export const APPLICATION_CAPABILITY_CONTRACT_VERSION = 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.inspect": ["describe"],
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.update": ["update"],
6
+ "records.edit": ["update", "rename"],
14
7
  "records.delete": ["delete"],
15
- "records.rename": ["rename"],
16
- "views.list": ["list_views"],
17
- "views.execute": ["execute_view"],
18
- "views.source.read": ["read_view_source"],
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
+ }
@@ -1,4 +1,4 @@
1
- import type { ApplicationFileRequirement } from "./files.js";
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: 1;
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?: ApplicationFileRequirement, operationTransportRecovery?: readonly OperationTransportProtocolVersion[]): ConnectContractRequirements;
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;
@@ -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 = 1;
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: 1
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 RELAY_CAPABILITIES: readonly ["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"];
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?: ApplicationRequirements;
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
- /** Access boundary requested after compatibility and provisioning checks. */
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
- * Exact contract definitions and sorted implementation sets approved by the
182
- * user. Digests make the scope fail closed if either the interface or any
183
- * provider changes after approval.
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
- connector_id: string;
523
- sequence: number;
524
- lease_issued_at_ms: number;
525
- lease_expires_at_ms: number;
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",
@@ -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
- ...validateCapabilityRequirements(value),
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
- if (Object.keys(capabilities).length === 0)
359
- return [];
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"));