@mdbase-dev/connect 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/README.md CHANGED
@@ -151,6 +151,46 @@ versioned capabilities in their manifest; they never maintain a parallel array
151
151
  of protocol operations. Use `getSnapshot()` and `subscribe()` directly or
152
152
  through your framework's external-store integration.
153
153
 
154
+ ### Legacy v1 and capability-group v2 declarations
155
+
156
+ The same SDK accepts both declaration versions without translating legacy exact
157
+ operations into broader groups. `requirements.capabilities.contract_version`
158
+ selects the semantics; an absent capability declaration means legacy v1.
159
+ `manifest_version` remains the document-format version, not this selector.
160
+
161
+ - **V1:** `authorize({ operations })`, `connection.requestOperations(operations)`,
162
+ and `session.ensureOperations(operations)` retain independent exact operations.
163
+ Incremental requests retain the existing exact grant. Legacy `files.actions`
164
+ and capability aliases (for example `records.update` and `files.read`) remain
165
+ supported. Bare registry/connection `authorize()` retains the predecessor's
166
+ `describe, changes, read, query` default; application sessions derive their
167
+ exact operation selection from the legacy capability declaration.
168
+ - **V2:** use `capabilities`, `requestCapabilities()`, and `ensureCapabilities()`.
169
+ Required groups are always included; omitted optional selection requests all
170
+ declared optional groups. Incremental requests retain already granted declared
171
+ groups. Files use `required`/`optional` actions.
172
+
173
+ Mixing `operations` and `capabilities`, or using an operations authorization API
174
+ with a v2 declaration, returns `invalid_application_manifest`, including for
175
+ JavaScript callers and already-sufficient grants. A rejected v2 request is never
176
+ retried as v1. Saved credentials and pending mutations are not translated or
177
+ replayed when selecting a semantic version; signed authorization retains its
178
+ registered declaration digest and exact recovery-transport requirements.
179
+
180
+ The SDK's `MdbaseAppManifest` and capability input types are explicit v1/v2
181
+ unions. `LegacyMdbaseAppManifest`, `LegacyApplicationRequirements`, and
182
+ `MdbaseApplicationRequirements` are available for explicit annotations. The
183
+ protocol package's canonical `ApplicationRequirements` remains v2-only.
184
+ Application sessions still require an explicit full-collection, versioned
185
+ capability declaration; no-capability legacy declarations can use the registry
186
+ or advanced session APIs.
187
+
188
+ This bridge is not a v2 readiness or rollout gate: structured v2 file,
189
+ notification, offline-runtime, and authority readiness evidence remains
190
+ incomplete. Legacy readiness aliases are evaluated only for v1. Positive server
191
+ and selected-authority v2 support gating remains separate work; this SDK change
192
+ does not advertise new support or authorize automatic consumer conversion.
193
+
154
194
  Before and during startup the session distinguishes `not_started`, `starting`,
155
195
  and `start_failed`; the last state carries the original typed problem and a
156
196
  later `start()` retries. `destroyed` is terminal. Once started, the session
@@ -304,22 +344,17 @@ documents both threat models. New or changed declaration criteria never silently
304
344
  broaden an existing grant: handle `notification_reauthorization_required` by
305
345
  running authorization again before retrying registration.
306
346
 
307
- Before opening a feature, inspect its exact authorization gap instead of
308
- waiting for an operation to fail:
347
+ Before opening a feature, request its declared capability group instead of
348
+ selecting individual transport operations:
309
349
 
310
350
  ```ts
311
- const required = ["read", "query", "update"] as const;
312
- const capabilities = connection.authorizationCapabilities([...required]);
313
- if (!capabilities.sufficient) {
314
- console.log("Needs", capabilities.missingOperations);
315
- await connection.requestOperations([...required]);
316
- }
351
+ await connection.requestCapabilities(["records.edit"]);
317
352
  ```
318
353
 
319
- `requestOperations()` is a no-op when the current grant is sufficient. When a
320
- replacement grant is needed, it requests the least-privilege union of the
321
- already granted operations and the missing requirements. An
322
- An `insufficient_access` problem carries the same `granted_operations`,
354
+ `requestCapabilities()` is a no-op when the current grant already includes the
355
+ complete group. When authorization is needed, required groups remain fixed and
356
+ the requested optional groups are expanded atomically. An
357
+ `insufficient_access` problem carries the internal `granted_operations`,
323
358
  `missing_operations`, and `required_operations` metadata with a `reauthorize`
324
359
  recovery action.
325
360
 
@@ -346,11 +381,11 @@ When the local connector definitively rejects an encrypted grant, the SDK does
346
381
  not bypass that decision through the relay. It classifies the error as requiring
347
382
  authorization, removes only the matching stale credential, and emits a `null`
348
383
  connection through `onConnectionChange()`. A subsequent
349
- `requestOperations()` call therefore starts authorization instead of trusting
384
+ `requestCapabilities()` call therefore starts authorization instead of trusting
350
385
  the stale cached capability list.
351
386
 
352
- Applications with full collection access can also register and maintain type
353
- definitions. Type source is returned with a revision token so updates cannot
387
+ Applications can register and maintain type definitions when their
388
+ collection-wide grant includes the corresponding operations. Type source is returned with a revision token so updates cannot
354
389
  silently overwrite a definition changed by another application:
355
390
 
356
391
  ```ts
@@ -379,7 +414,8 @@ if (!updatedType.ok) renderProblem(updatedType.problem);
379
414
  ```
380
415
 
381
416
  Request `read_type`, `create_type`, and `update_type` during authorization.
382
- Contract-scoped applications cannot manage collection-wide type definitions.
417
+ A legacy contract-scoped grant is inactive and must be reauthorized; contracts
418
+ themselves never confer type-management permission.
383
419
 
384
420
  To install a complete catalog pack without exposing its individual collection
385
421
  paths, request `assess_type_pack` and `apply_type_pack`, fetch and verify the
@@ -501,6 +537,7 @@ export const manifest = {
501
537
  homepage: "https://worklog.example",
502
538
  redirect_uris: ["https://worklog.example/auth/mdbase/callback"],
503
539
  requirements: {
540
+ access: "full_collection",
504
541
  contracts: workItemPack.provides
505
542
  },
506
543
  provisions: {
@@ -513,10 +550,11 @@ Set `collection_kind` to `hosted` when the application needs a durable
513
550
  provider-backed collection and the offline sync transport returned by
514
551
  `sync()`. Connect then offers and accepts collections with replication capability.
515
552
 
516
- Access defaults to the record types supplied by `requirements.contracts`.
517
- Set `access` to `full_collection` when the application needs collection-level
518
- features such as saved views. Required contracts still determine compatibility
519
- and are provisioned during approval.
553
+ Every SDK manifest must explicitly set `requirements.access` to the N-1 wire
554
+ spelling `full_collection`. An active grant covers the entire selected
555
+ collection; omission and legacy `contract` scope never satisfy application
556
+ access and require reauthorization. Required contracts remain separate
557
+ compatibility, setup, validation, and semantic-adapter evidence.
520
558
 
521
559
  `listViews()` returns each named view's selected result properties in display
522
560
  order. Property descriptors retain source labels for projected and computed
@@ -524,7 +562,7 @@ values. `executeView()` returns their values on each result row.
524
562
 
525
563
  Provisioning is part of the approval flow. The connector validates and
526
564
  installs each missing type pack transactionally, verifies its exact contracts
527
- and implementations, and creates the scoped grant afterward. The application
565
+ and implementations, and creates the collection grant afterward. The application
528
566
  is not granted collection-wide type-management access.
529
567
 
530
568
  The SDK returns typed outcomes, carries successful mdbase diagnostics alongside
@@ -0,0 +1,22 @@
1
+ import { type ApplicationCapabilityId, type ApplicationCapabilityRequirements, type LegacyApplicationCapabilityRequirements, type LegacyApplicationCapabilityId, type ApplicationRequirements, type LegacyApplicationRequirements, type MdbaseAppManifest, type LegacyMdbaseAppManifest, type CollectionOperation } from "@mdbase-dev/connect-protocol";
2
+ export declare const DEFAULT_OPERATIONS: CollectionOperation[];
3
+ /** SDK input compatibility boundary; protocol ApplicationRequirements remains v2-only. */
4
+ export type MdbaseApplicationManifest = MdbaseAppManifest | LegacyMdbaseAppManifest;
5
+ export type MdbaseApplicationRequirements = ApplicationRequirements | LegacyApplicationRequirements;
6
+ export type MdbaseApplicationCapabilityRequirements = ApplicationCapabilityRequirements | LegacyApplicationCapabilityRequirements;
7
+ export type MdbaseApplicationCapabilityId = ApplicationCapabilityId | LegacyApplicationCapabilityId;
8
+ interface MdbaseAuthorizationSelection {
9
+ operations?: CollectionOperation[];
10
+ capabilities?: MdbaseApplicationCapabilityId[];
11
+ }
12
+ export declare function semanticContractVersion(requirements: MdbaseApplicationRequirements): 1 | 2;
13
+ export declare function invalidAuthorizationSelection(message: string): never;
14
+ export declare function validateAuthorizationSelection(requirements: MdbaseApplicationRequirements, options: MdbaseAuthorizationSelection): void;
15
+ export declare function declaredOperations(requirements: MdbaseApplicationRequirements): CollectionOperation[];
16
+ export declare function authorizationOperations(application: {
17
+ requirements: MdbaseApplicationRequirements;
18
+ provisions?: MdbaseApplicationManifest["provisions"];
19
+ }, options: MdbaseAuthorizationSelection): CollectionOperation[];
20
+ export declare function retainedCapabilities(requirements: MdbaseApplicationRequirements, operations: CollectionOperation[]): MdbaseApplicationCapabilityId[];
21
+ export declare function authorizationFiles(requirements: MdbaseApplicationRequirements): import("@mdbase-dev/connect-protocol").ApplicationFileRequest | undefined;
22
+ export {};
@@ -0,0 +1,75 @@
1
+ import { APPLICATION_SETUP_OPERATIONS, applicationFileRequest, capabilityOperationsForContractVersion } from "@mdbase-dev/connect-protocol";
2
+ import { connectError } from "./errors.js";
3
+ export const DEFAULT_OPERATIONS = ["describe", "changes", "read", "query"];
4
+ export function semanticContractVersion(requirements) {
5
+ const version = requirements.capabilities === undefined ? 1 : requirements.capabilities?.contract_version;
6
+ if (version !== 1 && version !== 2)
7
+ invalidAuthorizationSelection("Unsupported declared capability contract version.");
8
+ return version;
9
+ }
10
+ export function invalidAuthorizationSelection(message) {
11
+ throw connectError("invalid_application_manifest", message, {
12
+ details: { issues: [{ path: "/requirements/capabilities", keyword: "authorizationSelection", message, params: {} }] }
13
+ });
14
+ }
15
+ export function validateAuthorizationSelection(requirements, options) {
16
+ if (options.operations !== undefined && options.capabilities !== undefined) {
17
+ invalidAuthorizationSelection("Specify operations or capabilities, not both.");
18
+ }
19
+ if (options.operations !== undefined && semanticContractVersion(requirements) !== 1) {
20
+ invalidAuthorizationSelection("Exact operations authorization is only supported for legacy v1 declarations.");
21
+ }
22
+ if (options.capabilities !== undefined) {
23
+ const declared = new Set([
24
+ ...(requirements.capabilities?.required ?? []),
25
+ ...(requirements.capabilities?.optional ?? [])
26
+ ]);
27
+ if (options.capabilities.some((id) => !declared.has(id)
28
+ || capabilityOperationsForContractVersion(semanticContractVersion(requirements), id) === undefined)) {
29
+ invalidAuthorizationSelection("Authorization can only include capabilities declared by the application.");
30
+ }
31
+ }
32
+ }
33
+ export function declaredOperations(requirements) {
34
+ const capabilities = requirements.capabilities;
35
+ if (!capabilities)
36
+ return [...DEFAULT_OPERATIONS];
37
+ return [...new Set([...capabilities.required, ...(capabilities.optional ?? [])].flatMap((id) => capabilityOperationsForContractVersion(capabilities.contract_version, id) ?? []))];
38
+ }
39
+ export function authorizationOperations(application, options) {
40
+ const { requirements } = application;
41
+ validateAuthorizationSelection(requirements, options);
42
+ // Legacy callers selected independent exact operations, including subsets of declarations.
43
+ if (semanticContractVersion(requirements) === 1) {
44
+ if (options.operations !== undefined)
45
+ return [...new Set(options.operations)];
46
+ if (options.capabilities === undefined)
47
+ return [...DEFAULT_OPERATIONS];
48
+ }
49
+ const declared = requirements.capabilities;
50
+ const selected = [...(declared?.required ?? []), ...(options.capabilities ?? declared?.optional ?? [])];
51
+ const operations = selected.flatMap((id) => capabilityOperationsForContractVersion(semanticContractVersion(requirements), id) ?? []);
52
+ const hasSetup = (application.provisions?.type_packs.length ?? 0) > 0
53
+ || (application.provisions?.configuration?.length ?? 0) > 0;
54
+ return [...new Set([
55
+ ...operations,
56
+ ...(semanticContractVersion(requirements) === 2 && hasSetup ? APPLICATION_SETUP_OPERATIONS : [])
57
+ ])];
58
+ }
59
+ export function retainedCapabilities(requirements, operations) {
60
+ return [...(requirements.capabilities?.required ?? []), ...(requirements.capabilities?.optional ?? [])].filter((id) => (capabilityOperationsForContractVersion(semanticContractVersion(requirements), id) ?? []).every((operation) => operations.includes(operation)));
61
+ }
62
+ export function authorizationFiles(requirements) {
63
+ const files = requirements.files;
64
+ if (!files)
65
+ return undefined;
66
+ if (semanticContractVersion(requirements) === 1) {
67
+ if (!("actions" in files) || "required" in files || "optional" in files) {
68
+ invalidAuthorizationSelection("Legacy file declarations must use exact files.actions.");
69
+ }
70
+ return { actions: [...files.actions], scope: structuredClone(files.scope) };
71
+ }
72
+ if ("actions" in files)
73
+ invalidAuthorizationSelection("V2 file declarations must use required/optional file actions.");
74
+ return applicationFileRequest(files);
75
+ }
@@ -1,5 +1,6 @@
1
- import { type ApplicationCapabilityId, type ConnectProblem, type JsonObject, type MdbaseAppManifest } from "@mdbase-dev/connect-protocol";
2
- import { type MdbaseEffectiveCapabilities } from "./capabilities.js";
1
+ import { type ConnectProblem, type JsonObject, type CollectionOperation } from "@mdbase-dev/connect-protocol";
2
+ import { type MdbaseApplicationManifest as MdbaseAppManifest, type MdbaseApplicationCapabilityId as ApplicationCapabilityId } from "./application-contract.js";
3
+ import { type ApplicationSessionContext } from "./structured-readiness.js";
3
4
  import type { MdbaseAuthorizationOutcome, MdbaseAuthorizeOptions, MdbaseConnection } from "./connection.js";
4
5
  import type { MdbaseConnectionInfo } from "./connection-types.js";
5
6
  import { type ConnectOutcome, type CollectionTypeProblemCode, type RegistrationProblemCode, type SessionProblemCode } from "./outcomes.js";
@@ -53,12 +54,6 @@ export interface MdbaseCollectionSetupUpdate {
53
54
  canApply: boolean;
54
55
  reason: string;
55
56
  }
56
- interface ApplicationSessionContext {
57
- collectionId: string;
58
- info: MdbaseConnectionInfo;
59
- capabilities: MdbaseEffectiveCapabilities;
60
- connections: MdbaseConnectionInfo[];
61
- }
62
57
  export type MdbaseApplicationSessionSnapshot = {
63
58
  status: "not_started";
64
59
  connections: MdbaseConnectionInfo[];
@@ -117,6 +112,7 @@ export declare class MdbaseApplicationSession<Frontmatter extends JsonObject = J
117
112
  private setupAssessment;
118
113
  private setupTypePackAdoptions;
119
114
  private verificationGeneration;
115
+ private readinessController?;
120
116
  private lifecycleGeneration;
121
117
  private readonly operationControllers;
122
118
  private startOperation;
@@ -142,17 +138,22 @@ export declare class MdbaseApplicationSession<Frontmatter extends JsonObject = J
142
138
  forget(collectionId: string): ConnectOutcome<void, SessionProblemCode>;
143
139
  authorize(target: "choose" | "selected" | {
144
140
  collectionId: string;
145
- }, options?: Omit<MdbaseAuthorizeOptions, "operations" | "returnTo" | "target">): Promise<ConnectOutcome<MdbaseAuthorizationOutcome<Frontmatter>, SessionProblemCode>>;
141
+ }, options?: Omit<MdbaseAuthorizeOptions, "returnTo" | "target">): Promise<ConnectOutcome<MdbaseAuthorizationOutcome<Frontmatter>, SessionProblemCode>>;
146
142
  handleAuthorizationCallback(callbackUrl: string | URL, options?: ConnectRequestOptions): Promise<ConnectOutcome<MdbaseConnection<Frontmatter>, SessionProblemCode>>;
147
143
  completeAuthorization(callbackUrl?: string | URL, options?: ConnectRequestOptions): Promise<ConnectOutcome<MdbaseConnection<Frontmatter>, SessionProblemCode>>;
148
144
  ensureCapabilities(capabilities: ApplicationCapabilityId[], options?: ConnectRequestOptions): Promise<ConnectOutcome<MdbaseAuthorizationOutcome<Frontmatter> | {
149
145
  kind: "unchanged";
150
146
  connection: MdbaseConnection<Frontmatter>;
151
147
  }, SessionProblemCode>>;
148
+ ensureOperations(operations: CollectionOperation[], options?: ConnectRequestOptions): Promise<ConnectOutcome<{
149
+ kind: "unchanged";
150
+ connection: MdbaseConnection<Frontmatter>;
151
+ } | MdbaseAuthorizationOutcome<Frontmatter>, SessionProblemCode>>;
152
152
  applyCollectionSetup(options?: ConnectRequestOptions): Promise<ConnectOutcome<MdbaseApplicationSessionSnapshot, CollectionTypeProblemCode | SessionProblemCode>>;
153
153
  private applyCollectionSetupStarted;
154
154
  private refresh;
155
155
  private verifySetup;
156
+ private publishReady;
156
157
  private collectionSetupInput;
157
158
  private context;
158
159
  private publish;
@@ -164,4 +165,3 @@ export declare class MdbaseApplicationSession<Frontmatter extends JsonObject = J
164
165
  private requireManifest;
165
166
  private requireApplication;
166
167
  }
167
- export {};
@@ -1,10 +1,11 @@
1
- import { capabilityOperations, operationsForApplicationCapabilities } from "@mdbase-dev/connect-protocol";
2
- /* The versioned protocol package is the sole capability-to-operation compiler. */
3
- import { effectiveCapabilities } from "./capabilities.js";
1
+ import { APPLICATION_SETUP_OPERATIONS } from "@mdbase-dev/connect-protocol";
2
+ import { declaredOperations, validateAuthorizationSelection } from "./application-contract.js";
3
+ import { applicationReadinessContext, collectionSetupInput, publishedStructuredReadiness, verifyStructuredReadiness, verificationKey, verificationValue } from "./structured-readiness.js";
4
4
  import { MdbaseConnectError, connectProblem } from "./errors.js";
5
5
  import { connectFailure, connectSuccess } from "./outcomes.js";
6
6
  import { createRequestBudget, resolveConnectTimeouts, withRequestBudget } from "./request-budget.js";
7
7
  import { defaultCallbackUrl } from "./runtime-utils.js";
8
+ import { declarationIdFromFamilyIdentity } from "./connect-authorization-helpers.js";
8
9
  import { MdbaseSession } from "./session.js";
9
10
  export class MdbaseMemoryVerificationStore {
10
11
  values = new Map();
@@ -37,6 +38,7 @@ export class MdbaseApplicationSession {
37
38
  setupAssessment = null;
38
39
  setupTypePackAdoptions = null;
39
40
  verificationGeneration = 0;
41
+ readinessController;
40
42
  lifecycleGeneration = 0;
41
43
  operationControllers = new Set();
42
44
  startOperation = null;
@@ -75,6 +77,7 @@ export class MdbaseApplicationSession {
75
77
  }
76
78
  abandonStart(operation) {
77
79
  operation.controller.abort();
80
+ this.readinessController?.abort();
78
81
  this.startOperation = null;
79
82
  this.lifecycleGeneration += 1;
80
83
  this.verificationGeneration += 1;
@@ -132,7 +135,19 @@ export class MdbaseApplicationSession {
132
135
  const manifest = await this.connect.manifest(options);
133
136
  if (!manifest.ok)
134
137
  return this.startFailure(manifest.problem, generation);
135
- const capabilities = manifest.value.requirements?.capabilities;
138
+ if (manifest.value.requirements?.access !== "full_collection") {
139
+ return this.startFailure(connectProblem("invalid_application_manifest", "Application sessions require explicit full_collection access.", {
140
+ details: {
141
+ issues: [{
142
+ path: "/requirements/access",
143
+ keyword: "const",
144
+ message: "must be full_collection",
145
+ params: { allowedValue: "full_collection" }
146
+ }]
147
+ }
148
+ }), generation);
149
+ }
150
+ const capabilities = manifest.value.requirements.capabilities;
136
151
  if (!capabilities) {
137
152
  return this.startFailure(connectProblem("invalid_application_manifest", "Application sessions require a versioned semantic capability contract.", {
138
153
  details: {
@@ -153,7 +168,7 @@ export class MdbaseApplicationSession {
153
168
  const base = new MdbaseSession(this.connect, {
154
169
  selection: this.options.selection,
155
170
  autoSelect: this.options.autoSelect,
156
- operations: operationsForSession(capabilities)
171
+ operations: operationsForSession(capabilities, manifest.value)
157
172
  });
158
173
  this.base = base;
159
174
  try {
@@ -197,6 +212,7 @@ export class MdbaseApplicationSession {
197
212
  return;
198
213
  this.lifecycleGeneration += 1;
199
214
  this.verificationGeneration += 1;
215
+ this.readinessController?.abort();
200
216
  this.startOperation?.controller.abort();
201
217
  this.startOperation = null;
202
218
  for (const controller of this.operationControllers)
@@ -293,6 +309,7 @@ export class MdbaseApplicationSession {
293
309
  }
294
310
  ensureCapabilities(capabilities, options) {
295
311
  return this.withLifecycleBase(options, (base, requestOptions, generation) => {
312
+ validateAuthorizationSelection(this.manifest.requirements, { ...options, capabilities });
296
313
  const manifestRequirements = this.manifest.requirements?.capabilities;
297
314
  const declared = new Set([
298
315
  ...(manifestRequirements?.required ?? []),
@@ -313,7 +330,7 @@ export class MdbaseApplicationSession {
313
330
  }
314
331
  })));
315
332
  }
316
- return base.ensureOperations(operationsForIds(capabilities), requestOptions).then(async (outcome) => {
333
+ return base.ensureCapabilities(capabilities, requestOptions).then(async (outcome) => {
317
334
  if (outcome.ok && outcome.value.kind === "connected" && this.lifecycleCurrent(generation)) {
318
335
  await this.refresh(true, requestOptions);
319
336
  }
@@ -321,6 +338,15 @@ export class MdbaseApplicationSession {
321
338
  });
322
339
  });
323
340
  }
341
+ ensureOperations(operations, options) {
342
+ return this.withLifecycleBase(options, async (base, requestOptions, generation) => {
343
+ validateAuthorizationSelection(this.manifest.requirements, { ...options, operations });
344
+ const outcome = await base.ensureOperations(operations, requestOptions);
345
+ if (outcome.ok && outcome.value.kind === "connected" && this.lifecycleCurrent(generation))
346
+ await this.refresh(true, requestOptions);
347
+ return outcome;
348
+ });
349
+ }
324
350
  applyCollectionSetup(options) {
325
351
  return this.withLifecycleBase(options, (_base, requestOptions, generation) => this.applyCollectionSetupStarted(requestOptions, generation));
326
352
  }
@@ -335,6 +361,7 @@ export class MdbaseApplicationSession {
335
361
  if (!connection) {
336
362
  return connectFailure(connectProblem("collection_not_ready", "The collection is not ready."));
337
363
  }
364
+ const verificationGeneration = this.verificationGeneration;
338
365
  const assessment = this.setupAssessment;
339
366
  const input = {
340
367
  ...this.collectionSetupInput(this.setupTypePackAdoptions ?? undefined),
@@ -352,6 +379,9 @@ export class MdbaseApplicationSession {
352
379
  }
353
380
  return connectFailure(connectProblem("session_destroyed", lifecycleProblemMessage("session_destroyed")));
354
381
  }
382
+ if (verificationGeneration !== this.verificationGeneration || this.connection() !== connection) {
383
+ return connectFailure(connectProblem("collection_not_ready", "The selected collection changed during setup."));
384
+ }
355
385
  if (!applied.ok) {
356
386
  if (applied.problem.code === "application_declaration_mismatch") {
357
387
  this.setupAssessment = null;
@@ -369,6 +399,7 @@ export class MdbaseApplicationSession {
369
399
  if (!this.base || !this.manifest)
370
400
  return;
371
401
  const current = this.base.getSnapshot();
402
+ this.readinessController?.abort();
372
403
  this.verificationGeneration += 1;
373
404
  const generation = this.verificationGeneration;
374
405
  this.setupAssessment = null;
@@ -389,18 +420,22 @@ export class MdbaseApplicationSession {
389
420
  return;
390
421
  }
391
422
  if (!context.capabilities.requiredAvailable
423
+ || context.readiness?.files.state === "requires_authorization"
392
424
  || !accessRequirementSatisfied(this.manifest, context.info)) {
393
425
  this.publish({ status: "authorization_required", ...context });
394
426
  return;
395
427
  }
396
- const managesSetup = this.manifest.requirements?.capabilities?.required
397
- .includes("collection.setup.apply") ?? false;
428
+ const managesSetup = (this.manifest.provisions?.type_packs.length ?? 0) > 0
429
+ || (this.manifest.provisions?.configuration?.length ?? 0) > 0;
398
430
  if (!managesSetup) {
399
- this.publish({ status: "ready", verification: "verified", ...context });
431
+ const verification = this.publishReady(context, generation, options);
432
+ if (awaitVerification)
433
+ await verification;
400
434
  return;
401
435
  }
402
436
  const key = verificationKey(this.manifest, current.collectionId);
403
- if (this.verificationStore.read(key) === verificationValue(this.manifest)) {
437
+ if (context.readiness?.contracts.state !== "checking"
438
+ && this.verificationStore.read(key) === verificationValue(this.manifest)) {
404
439
  this.publish({ status: "ready", verification: "cached", ...context });
405
440
  }
406
441
  else {
@@ -457,36 +492,51 @@ export class MdbaseApplicationSession {
457
492
  return;
458
493
  }
459
494
  this.verificationStore.write(verificationKey(manifest, context.collectionId), verificationValue(manifest));
460
- this.publish({ status: "ready", verification: "verified", ...context });
495
+ if (context.readiness)
496
+ context.readiness.setup = { state: "current", evidence: [{ source: "authority", fact: "Collection setup assessment is current." }] };
497
+ await this.publishReady(context, generation, options);
498
+ }
499
+ async publishReady(context, generation, options) {
500
+ const contracts = this.requireManifest().requirements?.contracts ?? [];
501
+ if (context.readiness && contracts.length) {
502
+ const connection = this.connection();
503
+ if (!connection || connection.collectionId !== context.collectionId)
504
+ return;
505
+ this.publish({ status: "checking_setup", ...context });
506
+ const controller = new AbortController();
507
+ this.readinessController?.abort();
508
+ this.readinessController = controller;
509
+ try {
510
+ if (!await verifyStructuredReadiness({
511
+ ...context, readiness: context.readiness, contracts, connection, controller, options,
512
+ requestMs: this.timeouts.requestMs,
513
+ isCurrent: () => generation === this.verificationGeneration && this.connection() === connection,
514
+ publish: result => this.publish({ ...result, ...context })
515
+ }))
516
+ return;
517
+ }
518
+ finally {
519
+ if (this.readinessController === controller)
520
+ this.readinessController = undefined;
521
+ }
522
+ }
523
+ if (generation === this.verificationGeneration)
524
+ this.publish({ status: "ready", verification: "verified", ...context });
461
525
  }
462
526
  collectionSetupInput(typePackAdoptions) {
463
527
  const manifest = this.requireManifest();
464
528
  const application = this.requireApplication();
465
- return {
466
- applicationId: declarationIdFromFamilyIdentity(application.family_identity),
467
- declarationDigest: `sha256:${application.manifest_digest}`,
468
- requirements: {
469
- configuration: manifest.requirements?.configuration ?? []
470
- },
471
- provisions: {
472
- configuration: manifest.provisions?.configuration ?? [],
473
- typePacks: manifest.provisions?.type_packs ?? []
474
- },
475
- ...(typePackAdoptions ? { typePackAdoptions } : {})
476
- };
529
+ return collectionSetupInput(manifest, declarationIdFromFamilyIdentity(application.family_identity), `sha256:${application.manifest_digest}`, typePackAdoptions);
477
530
  }
478
531
  context(connection) {
479
- const info = connection.info();
480
- return {
481
- collectionId: connection.collectionId,
482
- info,
483
- capabilities: effectiveCapabilities(this.requireManifest().requirements.capabilities, this.requireManifest(), info),
484
- connections: this.connect.connections()
485
- };
532
+ return applicationReadinessContext(this.requireManifest(), connection.collectionId, connection.info(), this.connect.connections());
486
533
  }
487
534
  publish(snapshot) {
488
535
  if (this.snapshot.status === "destroyed" && snapshot.status !== "destroyed")
489
536
  return;
537
+ if ("readiness" in snapshot && snapshot.readiness) {
538
+ snapshot = { ...snapshot, readiness: publishedStructuredReadiness(snapshot.readiness, snapshot.status, snapshot.status === "ready" ? snapshot.verification : undefined) };
539
+ }
490
540
  if (JSON.stringify(this.snapshot) === JSON.stringify(snapshot))
491
541
  return;
492
542
  this.snapshot = snapshot;
@@ -582,12 +632,13 @@ export class MdbaseApplicationSession {
582
632
  return this.application;
583
633
  }
584
634
  }
585
- function operationsForSession(requirements) {
586
- return operationsForApplicationCapabilities(requirements);
587
- }
588
- function operationsForIds(capabilities) {
589
- const definitions = capabilities.flatMap((id) => capabilityOperations(id));
590
- return [...new Set(definitions)];
635
+ function operationsForSession(requirements, manifest) {
636
+ const hasSetup = (manifest.provisions?.type_packs.length ?? 0) > 0
637
+ || (manifest.provisions?.configuration?.length ?? 0) > 0;
638
+ return [
639
+ ...declaredOperations(manifest.requirements),
640
+ ...(requirements.contract_version === 2 && hasSetup ? APPLICATION_SETUP_OPERATIONS : [])
641
+ ];
591
642
  }
592
643
  function definitionUpdate(provision, assessment) {
593
644
  const statusReason = assessment.status === "conflict"
@@ -647,26 +698,10 @@ function reviewableTypePackAdoptions(assessment) {
647
698
  }
648
699
  return adoptions;
649
700
  }
650
- function verificationKey(manifest, collectionId) {
651
- return `mdbase-application-session:v1:${manifest.id}:${collectionId}`;
652
- }
653
- function verificationValue(manifest) {
654
- return JSON.stringify({
655
- capabilities: manifest.requirements?.capabilities,
656
- requirements: manifest.requirements?.configuration ?? [],
657
- provisions: manifest.provisions ?? {}
658
- });
659
- }
660
701
  function accessRequirementSatisfied(manifest, connection) {
661
- return manifest.requirements?.access !== "full_collection"
662
- || connection.scope.access === "full_collection";
663
- }
664
- function declarationIdFromFamilyIdentity(familyIdentity) {
665
- const prefix = "bundle:";
666
- if (!familyIdentity.startsWith(prefix) || familyIdentity.length === prefix.length) {
667
- throw new Error("The registered application has no valid declaration identity.");
668
- }
669
- return familyIdentity.slice(prefix.length);
702
+ return manifest.requirements?.access === "full_collection"
703
+ && connection.scope.access === "full_collection"
704
+ && connection.scope.contracts.length === 0;
670
705
  }
671
706
  function lifecycleProblemMessage(code) {
672
707
  if (code === "session_destroyed")
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "file": "mdbase-connect.min.js",
3
- "integrity": "sha384-G9S7H692RFcmy7JJ8CrePVKzJb4fvERv7ZfJfsbaNMtdu0ffE1SRdTgZAyhrMtJk"
3
+ "integrity": "sha384-pt5qe5l2IuGgDjsId1oPNVT/zCR+0TLeEJNt6xKdZQSxAACXxaCqoxbZd37EriJh"
4
4
  }