@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/dist/session.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { APPLICATION_CAPABILITY_DEFINITIONS, capabilityOperationsForContractVersion } from "@mdbase-dev/connect-protocol";
2
+ import { retainedCapabilities, semanticContractVersion, validateAuthorizationSelection } from "./application-contract.js";
1
3
  import { authorizationCallbackState, authorizationReturnToFromProblem, isAuthorizationCallbackUrl } from "./authorization-url.js";
2
4
  import { MdbaseConnectError, connectProblem } from "./errors.js";
3
5
  import { DEFAULT_OPERATIONS } from "./internal-types.js";
@@ -221,9 +223,16 @@ export class MdbaseSession {
221
223
  const operation = this.beginLifecycleOperation(options);
222
224
  this.transactionDepth += 1;
223
225
  try {
226
+ const declaration = await this.connect.manifest?.(operation.options);
227
+ if (declaration && !declaration.ok)
228
+ return declaration;
229
+ if (!this.lifecycleCurrent(operation.generation))
230
+ return this.destroyedOutcome(connectSuccess(undefined));
231
+ const version = declaration ? semanticContractVersion(declaration.value.requirements) : 1;
224
232
  const outcome = await this.connect.authorize({
225
233
  ...operation.options,
226
- operations: this.operations,
234
+ ...(version === 1 && options.operations === undefined && options.capabilities === undefined
235
+ ? { operations: this.operations } : {}),
227
236
  target: targetCollectionId === null
228
237
  ? { kind: "choose" }
229
238
  : { kind: "collection", collectionId: targetCollectionId },
@@ -242,7 +251,13 @@ export class MdbaseSession {
242
251
  this.refresh();
243
252
  }
244
253
  }
245
- async ensureOperations(requiredOperations, options) {
254
+ ensureOperations(requiredOperations, options) {
255
+ return this.ensureAuthorization({ operations: requiredOperations }, options);
256
+ }
257
+ ensureCapabilities(requiredCapabilities, options) {
258
+ return this.ensureAuthorization({ capabilities: requiredCapabilities }, options);
259
+ }
260
+ async ensureAuthorization(selection, options) {
246
261
  const lifecycle = this.lifecycleProblem();
247
262
  if (lifecycle)
248
263
  return connectFailure(lifecycle);
@@ -250,17 +265,30 @@ export class MdbaseSession {
250
265
  if (current.status !== "ready") {
251
266
  return connectFailure(connectProblem("collection_not_ready", "Choose an authorized collection first."));
252
267
  }
253
- const required = uniqueOperations(requiredOperations);
254
- const capabilities = current.connection.authorizationCapabilities(required);
255
- if (capabilities.sufficient) {
256
- return connectSuccess({ kind: "unchanged", connection: current.connection });
257
- }
258
268
  const operation = this.beginLifecycleOperation(options);
259
269
  this.transactionDepth += 1;
260
270
  try {
271
+ const declaration = await this.connect.manifest?.(operation.options);
272
+ if (declaration && !declaration.ok)
273
+ return declaration;
274
+ if (!this.lifecycleCurrent(operation.generation))
275
+ return this.destroyedOutcome(connectSuccess(undefined));
276
+ // Custom advanced session adapters predating manifest() retain exact v1
277
+ // operations. The concrete SDK always supplies its declared manifest.
278
+ const requirements = declaration?.value.requirements ?? ("operations" in selection
279
+ ? { access: "full_collection", contracts: [] }
280
+ : { access: "full_collection", contracts: [], capabilities: { contract_version: 2, required: [], optional: Object.keys(APPLICATION_CAPABILITY_DEFINITIONS) } });
281
+ validateAuthorizationSelection(requirements, { ...options, ...selection });
282
+ const requiredOperations = "operations" in selection ? selection.operations : selection.capabilities.flatMap((id) => capabilityOperationsForContractVersion(semanticContractVersion(requirements), id) ?? []);
283
+ const authorization = current.connection.authorizationCapabilities(requiredOperations);
284
+ if (authorization.sufficient)
285
+ return connectSuccess({ kind: "unchanged", connection: current.connection });
286
+ const incremental = "operations" in selection || semanticContractVersion(requirements) === 1
287
+ ? { operations: uniqueOperations([...authorization.grantedOperations, ...requiredOperations]) }
288
+ : { capabilities: [...new Set([...retainedCapabilities(requirements, authorization.grantedOperations), ...selection.capabilities])] };
261
289
  const outcome = await this.connect.authorize({
262
290
  ...operation.options,
263
- operations: uniqueOperations([...capabilities.grantedOperations, ...capabilities.missingOperations]),
291
+ ...incremental,
264
292
  target: { kind: "collection", collectionId: current.collectionId },
265
293
  returnTo: this.selection.authorizationReturnTo()
266
294
  });
@@ -271,6 +299,11 @@ export class MdbaseSession {
271
299
  }
272
300
  return outcome;
273
301
  }
302
+ catch (error) {
303
+ if (error instanceof MdbaseConnectError)
304
+ return connectFailure(error.problem);
305
+ throw error;
306
+ }
274
307
  finally {
275
308
  operation.dispose();
276
309
  this.transactionDepth -= 1;
@@ -0,0 +1,77 @@
1
+ import type { ContractRequirement, FileAction, JsonObject } from "@mdbase-dev/connect-protocol";
2
+ import type { MdbaseApplicationManifest } from "./application-contract.js";
3
+ import type { MdbaseConnectionInfo } from "./connection-types.js";
4
+ import { type MdbaseEffectiveCapabilities, type MdbaseCapabilityEvidence } from "./capabilities.js";
5
+ import type { MdbaseConnection } from "./connection.js";
6
+ import type { ConnectRequestOptions } from "./operation-types.js";
7
+ type ReadinessPublication = {
8
+ status: "authorization_required";
9
+ } | {
10
+ status: "blocked";
11
+ problem: {
12
+ code: string;
13
+ message: string;
14
+ };
15
+ };
16
+ /** Live verification owns no session state; callers supply identity and cancellation guards. */
17
+ export declare function verifyStructuredReadiness<Frontmatter extends JsonObject>(input: {
18
+ collectionId: string;
19
+ readiness: MdbaseStructuredReadiness;
20
+ contracts: ContractRequirement[];
21
+ connection: MdbaseConnection<Frontmatter>;
22
+ controller: AbortController;
23
+ options?: ConnectRequestOptions;
24
+ requestMs: number | null;
25
+ isCurrent: () => boolean;
26
+ publish: (result: ReadinessPublication) => void;
27
+ }): Promise<boolean>;
28
+ export declare function publishedStructuredReadiness(source: MdbaseStructuredReadiness, status: string, verification?: "cached" | "verified"): MdbaseStructuredReadiness;
29
+ export interface ApplicationSessionContext {
30
+ collectionId: string;
31
+ info: MdbaseConnectionInfo;
32
+ capabilities: MdbaseEffectiveCapabilities;
33
+ /** Present only for semantic contract v2. */
34
+ readiness?: MdbaseStructuredReadiness;
35
+ connections: MdbaseConnectionInfo[];
36
+ }
37
+ export declare function applicationReadinessContext(manifest: MdbaseApplicationManifest, collectionId: string, info: MdbaseConnectionInfo, connections: MdbaseConnectionInfo[]): ApplicationSessionContext;
38
+ export declare function verificationKey(manifest: MdbaseApplicationManifest, collectionId: string): string;
39
+ export declare function verificationValue(manifest: MdbaseApplicationManifest): string;
40
+ export declare function collectionSetupInput(manifest: MdbaseApplicationManifest, applicationId: string, declarationDigest: string, typePackAdoptions?: Record<string, Record<string, string>>): {
41
+ applicationId: string;
42
+ declarationDigest: string;
43
+ requirements: {
44
+ configuration: import("@mdbase-dev/connect-protocol").ConfigurationRequirement[];
45
+ };
46
+ provisions: {
47
+ configuration: import("@mdbase-dev/connect-protocol").ConfigurationProvision[];
48
+ typePacks: import("@mdbase-dev/connect-protocol").TypePackProvision[];
49
+ };
50
+ typePackAdoptions?: Record<string, Record<string, string>> | undefined;
51
+ };
52
+ /** Independent v2 declarations; never legacy capability aliases. */
53
+ export interface MdbaseStructuredReadiness {
54
+ files: {
55
+ state: "not_required" | "available" | "requires_authorization";
56
+ missingRequiredActions: FileAction[];
57
+ missingOptionalActions: FileAction[];
58
+ scopeMatches: boolean;
59
+ evidence: MdbaseCapabilityEvidence[];
60
+ };
61
+ contracts: {
62
+ state: "not_required" | "checking" | "verified" | "requires_setup" | "requires_authorization" | "temporarily_unavailable";
63
+ missing: ContractRequirement[];
64
+ evidence: MdbaseCapabilityEvidence[];
65
+ };
66
+ setup: {
67
+ state: "not_required" | "checking" | "review_required" | "current" | "cached" | "blocked" | "requires_authorization";
68
+ evidence: MdbaseCapabilityEvidence[];
69
+ };
70
+ notifications: {
71
+ state: "not_required" | "requires_registration";
72
+ registration: "unverified";
73
+ evidence: MdbaseCapabilityEvidence[];
74
+ };
75
+ }
76
+ export declare function structuredReadiness(manifest: MdbaseApplicationManifest, info: MdbaseConnectionInfo): MdbaseStructuredReadiness;
77
+ export {};
@@ -0,0 +1,126 @@
1
+ /* The versioned protocol package is the sole capability-to-operation compiler. */
2
+ import { effectiveCapabilities } from "./capabilities.js";
3
+ import { MdbaseConnectError } from "./errors.js";
4
+ import { withRequestBudget } from "./request-budget.js";
5
+ /** Live verification owns no session state; callers supply identity and cancellation guards. */
6
+ export async function verifyStructuredReadiness(input) {
7
+ const { collectionId, readiness, contracts, connection, controller, options, requestMs, isCurrent, publish } = input;
8
+ const abort = () => controller.abort(options?.signal?.reason);
9
+ if (options?.signal?.aborted)
10
+ abort();
11
+ else
12
+ options?.signal?.addEventListener("abort", abort, { once: true });
13
+ try {
14
+ const outcome = await withRequestBudget({ ...options, signal: controller.signal }, requestMs, request => connection.describe(request));
15
+ if (!isCurrent())
16
+ return false;
17
+ if (!outcome.ok) {
18
+ const authorizationRequired = ["access_denied", "collection_access_denied", "insufficient_access", "not_authorized", "authorization_expired", "application_declaration_mismatch"].includes(outcome.problem.code);
19
+ readiness.contracts = { state: authorizationRequired ? "requires_authorization" : "temporarily_unavailable", missing: [], evidence: [{ source: "runtime", fact: outcome.problem.message }] };
20
+ publish(authorizationRequired
21
+ ? { status: "authorization_required" }
22
+ : { status: "blocked", problem: outcome.problem });
23
+ return false;
24
+ }
25
+ if (outcome.value.collectionId !== collectionId) {
26
+ readiness.contracts = { state: "temporarily_unavailable", missing: [], evidence: [] };
27
+ publish({ status: "blocked", problem: { code: "collection_not_ready", message: "Description belongs to a different collection." } });
28
+ return false;
29
+ }
30
+ const missing = contracts.filter(required => !outcome.value.contracts.some(actual => actual.id === required.id && actual.version === required.version && actual.digest === required.digest));
31
+ readiness.contracts = { state: missing.length ? "requires_setup" : "verified", missing,
32
+ evidence: [{ source: "authority", fact: "Exact contract ID, version and digest checked by live describe for this collection." }] };
33
+ if (missing.length) {
34
+ publish({ status: "blocked", problem: { code: "collection_not_ready", message: "The collection is missing required exact contracts." } });
35
+ return false;
36
+ }
37
+ }
38
+ catch (error) {
39
+ if (!isCurrent())
40
+ return false;
41
+ if (!(error instanceof MdbaseConnectError))
42
+ throw error;
43
+ readiness.contracts = { state: "temporarily_unavailable", missing: [], evidence: [{ source: "runtime", fact: error.problem.message }] };
44
+ publish({ status: "blocked", problem: error.problem });
45
+ return false;
46
+ }
47
+ finally {
48
+ options?.signal?.removeEventListener("abort", abort);
49
+ }
50
+ return true;
51
+ }
52
+ export function publishedStructuredReadiness(source, status, verification) {
53
+ const readiness = structuredClone(source);
54
+ if (readiness.setup.state !== "not_required" && readiness.setup.state !== "current") {
55
+ readiness.setup.state = status === "ready" ? (verification === "cached" ? "cached" : "current")
56
+ : status === "setup_review_required" ? "review_required"
57
+ : status === "authorization_required" ? "requires_authorization"
58
+ : status === "blocked" ? "blocked" : "checking";
59
+ readiness.setup.evidence = [{ source: status === "ready" && verification === "cached" ? "runtime" : "authority",
60
+ fact: `Collection setup verification: ${readiness.setup.state}.` }];
61
+ }
62
+ return readiness;
63
+ }
64
+ export function applicationReadinessContext(manifest, collectionId, info, connections) {
65
+ return {
66
+ collectionId, info,
67
+ capabilities: effectiveCapabilities(manifest.requirements.capabilities, manifest, info),
68
+ ...(manifest.requirements?.capabilities?.contract_version === 2
69
+ ? { readiness: structuredReadiness(manifest, info) } : {}),
70
+ connections
71
+ };
72
+ }
73
+ export function verificationKey(manifest, collectionId) {
74
+ return `mdbase-application-session:v1:${manifest.id}:${collectionId}`;
75
+ }
76
+ export function verificationValue(manifest) {
77
+ return JSON.stringify({
78
+ capabilities: manifest.requirements?.capabilities,
79
+ requirements: manifest.requirements?.configuration ?? [],
80
+ provisions: manifest.provisions ?? {}
81
+ });
82
+ }
83
+ export function collectionSetupInput(manifest, applicationId, declarationDigest, typePackAdoptions) {
84
+ return {
85
+ applicationId, declarationDigest,
86
+ requirements: { configuration: manifest.requirements?.configuration ?? [] },
87
+ provisions: {
88
+ configuration: manifest.provisions?.configuration ?? [],
89
+ typePacks: manifest.provisions?.type_packs ?? []
90
+ },
91
+ ...(typePackAdoptions ? { typePackAdoptions } : {})
92
+ };
93
+ }
94
+ function sameScope(left, right) {
95
+ if (!right || left.kind !== right.kind)
96
+ return false;
97
+ if (left.kind === "collection" || right.kind === "collection")
98
+ return true;
99
+ const a = [...new Set(left.folders)].sort();
100
+ const b = [...new Set(right.folders)].sort();
101
+ return JSON.stringify(a) === JSON.stringify(b);
102
+ }
103
+ export function structuredReadiness(manifest, info) {
104
+ const files = manifest.requirements?.files;
105
+ const requirement = files && "required" in files ? files : undefined;
106
+ const scopeMatches = !requirement || sameScope(requirement.scope, info.fileCapability?.scope);
107
+ const missing = (actions) => actions.filter(action => !scopeMatches || !info.fileCapability?.actions.includes(action));
108
+ const missingRequiredActions = missing(requirement?.required ?? []);
109
+ const missingOptionalActions = missing(requirement?.optional ?? []);
110
+ const contracts = manifest.requirements?.contracts ?? [];
111
+ const setup = (manifest.provisions?.type_packs.length ?? 0) > 0 || (manifest.provisions?.configuration?.length ?? 0) > 0;
112
+ return {
113
+ files: {
114
+ state: !requirement ? "not_required" : missingRequiredActions.length ? "requires_authorization" : "available",
115
+ missingRequiredActions, missingOptionalActions, scopeMatches,
116
+ evidence: [{ source: "authorization", fact: "Compared exact file actions and folder scope with connection.info().fileCapability." }]
117
+ },
118
+ contracts: { state: contracts.length ? "checking" : "not_required", missing: [], evidence: [] },
119
+ setup: { state: setup ? "checking" : "not_required", evidence: [] },
120
+ notifications: {
121
+ state: manifest.notifications?.criteria.length ? "requires_registration" : "not_required",
122
+ registration: "unverified",
123
+ evidence: [{ source: "runtime", fact: "The connection API exposes registration commands but no current delivery-registration query. Manifest criteria do not prove approval or delivery registration." }]
124
+ }
125
+ };
126
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mdbase-dev/connect",
3
- "version": "0.1.0-beta.94",
3
+ "version": "0.1.0-beta.96",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -33,7 +33,7 @@
33
33
  "README.md"
34
34
  ],
35
35
  "dependencies": {
36
- "@mdbase-dev/connect-protocol": "0.1.0-beta.94"
36
+ "@mdbase-dev/connect-protocol": "0.1.0-beta.96"
37
37
  },
38
38
  "devDependencies": {
39
39
  "esbuild": "^0.28.1",