@mdbase-dev/connect 0.1.0-beta.94 → 0.1.0-beta.96
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 +48 -13
- package/dist/application-contract.d.ts +22 -0
- package/dist/application-contract.js +75 -0
- package/dist/application-session.d.ts +10 -10
- package/dist/application-session.js +74 -52
- package/dist/browser/integrity.json +1 -1
- package/dist/browser/mdbase-connect.min.js +3 -3
- package/dist/browser/mdbase-connect.min.js.sha384 +1 -1
- package/dist/capabilities.d.ts +4 -3
- package/dist/capabilities.js +15 -31
- package/dist/connect-authorization-helpers.d.ts +1 -0
- package/dist/connect-authorization-helpers.js +12 -0
- package/dist/connect-options.d.ts +2 -1
- package/dist/connect.d.ts +2 -1
- package/dist/connect.js +25 -25
- package/dist/connection.d.ts +11 -0
- package/dist/connection.js +38 -11
- package/dist/index.d.ts +3 -1
- package/dist/internal-types.d.ts +4 -2
- package/dist/internal-types.js +1 -1
- package/dist/mdbase-connect.d.ts +1 -1
- package/dist/operation-types.d.ts +4 -0
- package/dist/query-pagination.js +6 -3
- package/dist/request-coordinator.js +1 -0
- package/dist/runtime-utils.d.ts +8 -1
- package/dist/runtime-utils.js +13 -0
- package/dist/session.d.ts +11 -4
- package/dist/session.js +41 -8
- package/dist/structured-readiness.d.ts +77 -0
- package/dist/structured-readiness.js +126 -0
- package/package.json +2 -2
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,
|
|
308
|
-
|
|
347
|
+
Before opening a feature, request its declared capability group instead of
|
|
348
|
+
selecting individual transport operations:
|
|
309
349
|
|
|
310
350
|
```ts
|
|
311
|
-
|
|
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
|
-
`
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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,7 +381,7 @@ 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
|
-
`
|
|
384
|
+
`requestCapabilities()` call therefore starts authorization instead of trusting
|
|
350
385
|
the stale cached capability list.
|
|
351
386
|
|
|
352
387
|
Applications can register and maintain type definitions when their
|
|
@@ -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
|
|
2
|
-
import { type
|
|
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, "
|
|
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 {
|
|
2
|
-
|
|
3
|
-
import {
|
|
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;
|
|
@@ -165,7 +168,7 @@ export class MdbaseApplicationSession {
|
|
|
165
168
|
const base = new MdbaseSession(this.connect, {
|
|
166
169
|
selection: this.options.selection,
|
|
167
170
|
autoSelect: this.options.autoSelect,
|
|
168
|
-
operations: operationsForSession(capabilities)
|
|
171
|
+
operations: operationsForSession(capabilities, manifest.value)
|
|
169
172
|
});
|
|
170
173
|
this.base = base;
|
|
171
174
|
try {
|
|
@@ -209,6 +212,7 @@ export class MdbaseApplicationSession {
|
|
|
209
212
|
return;
|
|
210
213
|
this.lifecycleGeneration += 1;
|
|
211
214
|
this.verificationGeneration += 1;
|
|
215
|
+
this.readinessController?.abort();
|
|
212
216
|
this.startOperation?.controller.abort();
|
|
213
217
|
this.startOperation = null;
|
|
214
218
|
for (const controller of this.operationControllers)
|
|
@@ -305,6 +309,7 @@ export class MdbaseApplicationSession {
|
|
|
305
309
|
}
|
|
306
310
|
ensureCapabilities(capabilities, options) {
|
|
307
311
|
return this.withLifecycleBase(options, (base, requestOptions, generation) => {
|
|
312
|
+
validateAuthorizationSelection(this.manifest.requirements, { ...options, capabilities });
|
|
308
313
|
const manifestRequirements = this.manifest.requirements?.capabilities;
|
|
309
314
|
const declared = new Set([
|
|
310
315
|
...(manifestRequirements?.required ?? []),
|
|
@@ -325,7 +330,7 @@ export class MdbaseApplicationSession {
|
|
|
325
330
|
}
|
|
326
331
|
})));
|
|
327
332
|
}
|
|
328
|
-
return base.
|
|
333
|
+
return base.ensureCapabilities(capabilities, requestOptions).then(async (outcome) => {
|
|
329
334
|
if (outcome.ok && outcome.value.kind === "connected" && this.lifecycleCurrent(generation)) {
|
|
330
335
|
await this.refresh(true, requestOptions);
|
|
331
336
|
}
|
|
@@ -333,6 +338,15 @@ export class MdbaseApplicationSession {
|
|
|
333
338
|
});
|
|
334
339
|
});
|
|
335
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
|
+
}
|
|
336
350
|
applyCollectionSetup(options) {
|
|
337
351
|
return this.withLifecycleBase(options, (_base, requestOptions, generation) => this.applyCollectionSetupStarted(requestOptions, generation));
|
|
338
352
|
}
|
|
@@ -347,6 +361,7 @@ export class MdbaseApplicationSession {
|
|
|
347
361
|
if (!connection) {
|
|
348
362
|
return connectFailure(connectProblem("collection_not_ready", "The collection is not ready."));
|
|
349
363
|
}
|
|
364
|
+
const verificationGeneration = this.verificationGeneration;
|
|
350
365
|
const assessment = this.setupAssessment;
|
|
351
366
|
const input = {
|
|
352
367
|
...this.collectionSetupInput(this.setupTypePackAdoptions ?? undefined),
|
|
@@ -364,6 +379,9 @@ export class MdbaseApplicationSession {
|
|
|
364
379
|
}
|
|
365
380
|
return connectFailure(connectProblem("session_destroyed", lifecycleProblemMessage("session_destroyed")));
|
|
366
381
|
}
|
|
382
|
+
if (verificationGeneration !== this.verificationGeneration || this.connection() !== connection) {
|
|
383
|
+
return connectFailure(connectProblem("collection_not_ready", "The selected collection changed during setup."));
|
|
384
|
+
}
|
|
367
385
|
if (!applied.ok) {
|
|
368
386
|
if (applied.problem.code === "application_declaration_mismatch") {
|
|
369
387
|
this.setupAssessment = null;
|
|
@@ -381,6 +399,7 @@ export class MdbaseApplicationSession {
|
|
|
381
399
|
if (!this.base || !this.manifest)
|
|
382
400
|
return;
|
|
383
401
|
const current = this.base.getSnapshot();
|
|
402
|
+
this.readinessController?.abort();
|
|
384
403
|
this.verificationGeneration += 1;
|
|
385
404
|
const generation = this.verificationGeneration;
|
|
386
405
|
this.setupAssessment = null;
|
|
@@ -401,18 +420,22 @@ export class MdbaseApplicationSession {
|
|
|
401
420
|
return;
|
|
402
421
|
}
|
|
403
422
|
if (!context.capabilities.requiredAvailable
|
|
423
|
+
|| context.readiness?.files.state === "requires_authorization"
|
|
404
424
|
|| !accessRequirementSatisfied(this.manifest, context.info)) {
|
|
405
425
|
this.publish({ status: "authorization_required", ...context });
|
|
406
426
|
return;
|
|
407
427
|
}
|
|
408
|
-
const managesSetup = this.manifest.
|
|
409
|
-
|
|
428
|
+
const managesSetup = (this.manifest.provisions?.type_packs.length ?? 0) > 0
|
|
429
|
+
|| (this.manifest.provisions?.configuration?.length ?? 0) > 0;
|
|
410
430
|
if (!managesSetup) {
|
|
411
|
-
this.
|
|
431
|
+
const verification = this.publishReady(context, generation, options);
|
|
432
|
+
if (awaitVerification)
|
|
433
|
+
await verification;
|
|
412
434
|
return;
|
|
413
435
|
}
|
|
414
436
|
const key = verificationKey(this.manifest, current.collectionId);
|
|
415
|
-
if (
|
|
437
|
+
if (context.readiness?.contracts.state !== "checking"
|
|
438
|
+
&& this.verificationStore.read(key) === verificationValue(this.manifest)) {
|
|
416
439
|
this.publish({ status: "ready", verification: "cached", ...context });
|
|
417
440
|
}
|
|
418
441
|
else {
|
|
@@ -469,36 +492,51 @@ export class MdbaseApplicationSession {
|
|
|
469
492
|
return;
|
|
470
493
|
}
|
|
471
494
|
this.verificationStore.write(verificationKey(manifest, context.collectionId), verificationValue(manifest));
|
|
472
|
-
|
|
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 });
|
|
473
525
|
}
|
|
474
526
|
collectionSetupInput(typePackAdoptions) {
|
|
475
527
|
const manifest = this.requireManifest();
|
|
476
528
|
const application = this.requireApplication();
|
|
477
|
-
return {
|
|
478
|
-
applicationId: declarationIdFromFamilyIdentity(application.family_identity),
|
|
479
|
-
declarationDigest: `sha256:${application.manifest_digest}`,
|
|
480
|
-
requirements: {
|
|
481
|
-
configuration: manifest.requirements?.configuration ?? []
|
|
482
|
-
},
|
|
483
|
-
provisions: {
|
|
484
|
-
configuration: manifest.provisions?.configuration ?? [],
|
|
485
|
-
typePacks: manifest.provisions?.type_packs ?? []
|
|
486
|
-
},
|
|
487
|
-
...(typePackAdoptions ? { typePackAdoptions } : {})
|
|
488
|
-
};
|
|
529
|
+
return collectionSetupInput(manifest, declarationIdFromFamilyIdentity(application.family_identity), `sha256:${application.manifest_digest}`, typePackAdoptions);
|
|
489
530
|
}
|
|
490
531
|
context(connection) {
|
|
491
|
-
|
|
492
|
-
return {
|
|
493
|
-
collectionId: connection.collectionId,
|
|
494
|
-
info,
|
|
495
|
-
capabilities: effectiveCapabilities(this.requireManifest().requirements.capabilities, this.requireManifest(), info),
|
|
496
|
-
connections: this.connect.connections()
|
|
497
|
-
};
|
|
532
|
+
return applicationReadinessContext(this.requireManifest(), connection.collectionId, connection.info(), this.connect.connections());
|
|
498
533
|
}
|
|
499
534
|
publish(snapshot) {
|
|
500
535
|
if (this.snapshot.status === "destroyed" && snapshot.status !== "destroyed")
|
|
501
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
|
+
}
|
|
502
540
|
if (JSON.stringify(this.snapshot) === JSON.stringify(snapshot))
|
|
503
541
|
return;
|
|
504
542
|
this.snapshot = snapshot;
|
|
@@ -594,12 +632,13 @@ export class MdbaseApplicationSession {
|
|
|
594
632
|
return this.application;
|
|
595
633
|
}
|
|
596
634
|
}
|
|
597
|
-
function operationsForSession(requirements) {
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
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
|
+
];
|
|
603
642
|
}
|
|
604
643
|
function definitionUpdate(provision, assessment) {
|
|
605
644
|
const statusReason = assessment.status === "conflict"
|
|
@@ -659,28 +698,11 @@ function reviewableTypePackAdoptions(assessment) {
|
|
|
659
698
|
}
|
|
660
699
|
return adoptions;
|
|
661
700
|
}
|
|
662
|
-
function verificationKey(manifest, collectionId) {
|
|
663
|
-
return `mdbase-application-session:v1:${manifest.id}:${collectionId}`;
|
|
664
|
-
}
|
|
665
|
-
function verificationValue(manifest) {
|
|
666
|
-
return JSON.stringify({
|
|
667
|
-
capabilities: manifest.requirements?.capabilities,
|
|
668
|
-
requirements: manifest.requirements?.configuration ?? [],
|
|
669
|
-
provisions: manifest.provisions ?? {}
|
|
670
|
-
});
|
|
671
|
-
}
|
|
672
701
|
function accessRequirementSatisfied(manifest, connection) {
|
|
673
702
|
return manifest.requirements?.access === "full_collection"
|
|
674
703
|
&& connection.scope.access === "full_collection"
|
|
675
704
|
&& connection.scope.contracts.length === 0;
|
|
676
705
|
}
|
|
677
|
-
function declarationIdFromFamilyIdentity(familyIdentity) {
|
|
678
|
-
const prefix = "bundle:";
|
|
679
|
-
if (!familyIdentity.startsWith(prefix) || familyIdentity.length === prefix.length) {
|
|
680
|
-
throw new Error("The registered application has no valid declaration identity.");
|
|
681
|
-
}
|
|
682
|
-
return familyIdentity.slice(prefix.length);
|
|
683
|
-
}
|
|
684
706
|
function lifecycleProblemMessage(code) {
|
|
685
707
|
if (code === "session_destroyed")
|
|
686
708
|
return "This application session has been destroyed.";
|