@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 +59 -21
- 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 +90 -55
- 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 +25 -47
- package/dist/connect-options.d.ts +2 -1
- package/dist/connect.d.ts +2 -1
- package/dist/connect.js +41 -34
- package/dist/connection-transport-internals.d.ts +1 -1
- package/dist/connection-transport.js +1 -1
- 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 -3
- package/dist/outcomes.d.ts +3 -3
- package/dist/outcomes.js +1 -0
- package/dist/request-coordinator.js +1 -0
- package/dist/runtime-utils.d.ts +11 -1
- package/dist/runtime-utils.js +18 -0
- package/dist/session.d.ts +12 -5
- package/dist/session.js +41 -8
- package/dist/stored-token.d.ts +1 -1
- package/dist/stored-token.js +8 -4
- package/dist/structured-readiness.d.ts +77 -0
- package/dist/structured-readiness.js +126 -0
- package/dist/token-response.js +4 -1
- package/package.json +2 -2
package/dist/runtime-utils.js
CHANGED
|
@@ -2,6 +2,19 @@ import { validateGrantEncryption } from "./crypto.js";
|
|
|
2
2
|
import { connectError, serverConnectError } from "./errors.js";
|
|
3
3
|
import { canonicalJson } from "./operation-helpers.js";
|
|
4
4
|
import { bytesToBase64Url, randomBase64Url } from "./base64.js";
|
|
5
|
+
/** Resolve bundled declarations and their stable storage identity without loading URLs. */
|
|
6
|
+
export function resolveManifestSource(input, redirect) {
|
|
7
|
+
const manifest = input instanceof URL ? input.href : input ?? defaultManifestSource();
|
|
8
|
+
const manifestSource = typeof manifest === "string"
|
|
9
|
+
? manifest
|
|
10
|
+
: manifest.distribution === "portable"
|
|
11
|
+
? `bundle:${manifest.id}:${manifestStorageFingerprint(manifest)}`
|
|
12
|
+
: `bundle:${manifest.id}`;
|
|
13
|
+
const redirectUri = redirect === undefined ? (typeof manifest !== "string" && manifest.distribution === "portable"
|
|
14
|
+
? ""
|
|
15
|
+
: defaultRedirectUri()) : String(redirect);
|
|
16
|
+
return { manifest, manifestSource, redirectUri };
|
|
17
|
+
}
|
|
5
18
|
/** Normalize fetch rejection at the exact I/O boundary. */
|
|
6
19
|
export async function connectFetch(input, init, code, message) {
|
|
7
20
|
try {
|
|
@@ -215,6 +228,7 @@ export function parseStored(value) {
|
|
|
215
228
|
return null;
|
|
216
229
|
}
|
|
217
230
|
}
|
|
231
|
+
/** Parse both canonical and N-1 grant shapes so callers can diagnose legacy grants. */
|
|
218
232
|
export function parseGrantScope(value) {
|
|
219
233
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
220
234
|
return null;
|
|
@@ -246,6 +260,10 @@ export function parseGrantScope(value) {
|
|
|
246
260
|
return null;
|
|
247
261
|
return scope;
|
|
248
262
|
}
|
|
263
|
+
/** Only this scope authorizes application data access. */
|
|
264
|
+
export function isCanonicalGrantScope(scope) {
|
|
265
|
+
return scope.access === "full_collection" && scope.contracts.length === 0;
|
|
266
|
+
}
|
|
249
267
|
export function stripTrailingSlash(value) {
|
|
250
268
|
return value.endsWith("/") ? value.slice(0, -1) : value;
|
|
251
269
|
}
|
package/dist/session.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { CollectionOperation, ConnectProblem, JsonObject } from "@mdbase-dev/connect-protocol";
|
|
2
|
+
import { type MdbaseApplicationManifest, type MdbaseApplicationCapabilityId as ApplicationCapabilityId } from "./application-contract.js";
|
|
2
3
|
import { MdbaseConnection, type MdbaseAuthorizationOutcome, type MdbaseAuthorizationResult, type MdbaseAuthorizeOptions } from "./connection.js";
|
|
3
4
|
import type { MdbaseAuthorizationCapabilities, MdbaseConnectionInfo } from "./connection-types.js";
|
|
4
|
-
import { type AuthorizationProblemCode, type ConnectOutcome, type SessionProblemCode } from "./outcomes.js";
|
|
5
|
+
import { type AuthorizationProblemCode, type RegistrationProblemCode, type ConnectOutcome, type SessionProblemCode } from "./outcomes.js";
|
|
5
6
|
import type { MdbaseSelectionHistory, MdbaseApplicationSelection } from "./selection.js";
|
|
6
7
|
import type { ConnectRequestOptions } from "./operation-types.js";
|
|
7
8
|
export interface MdbaseSessionConnect<Frontmatter extends JsonObject> {
|
|
9
|
+
manifest?(options?: ConnectRequestOptions): Promise<ConnectOutcome<MdbaseApplicationManifest, RegistrationProblemCode>>;
|
|
8
10
|
authorize(options?: MdbaseAuthorizeOptions): Promise<ConnectOutcome<MdbaseAuthorizationOutcome<Frontmatter>, AuthorizationProblemCode>>;
|
|
9
11
|
completeAuthorization(callbackUrl: string, options?: ConnectRequestOptions): Promise<ConnectOutcome<MdbaseAuthorizationResult<Frontmatter>, AuthorizationProblemCode>>;
|
|
10
12
|
connections(): MdbaseConnectionInfo[];
|
|
@@ -17,7 +19,7 @@ export interface MdbaseSessionOptions {
|
|
|
17
19
|
operations?: CollectionOperation[];
|
|
18
20
|
autoSelect?: "only" | "never";
|
|
19
21
|
}
|
|
20
|
-
export type MdbaseUnavailableReason = "not_authorized" | "authorization_lost" | "invalid_stored_grant";
|
|
22
|
+
export type MdbaseUnavailableReason = "not_authorized" | "authorization_lost" | "invalid_stored_grant" | "legacy_scope_reauthorization_required";
|
|
21
23
|
export type MdbaseSessionSnapshot<Frontmatter extends JsonObject = JsonObject> = {
|
|
22
24
|
status: "not_started";
|
|
23
25
|
connections: MdbaseConnectionInfo[];
|
|
@@ -87,11 +89,16 @@ export declare class MdbaseSession<Frontmatter extends JsonObject = JsonObject>
|
|
|
87
89
|
forget(collectionId: string): ConnectOutcome<void, SessionProblemCode>;
|
|
88
90
|
authorize(target: "choose" | "selected" | {
|
|
89
91
|
collectionId: string;
|
|
90
|
-
}, options?: Omit<MdbaseAuthorizeOptions, "
|
|
91
|
-
ensureOperations(requiredOperations: CollectionOperation[], options?: ConnectRequestOptions): Promise<ConnectOutcome<
|
|
92
|
+
}, options?: Omit<MdbaseAuthorizeOptions, "returnTo" | "target">): Promise<ConnectOutcome<MdbaseAuthorizationOutcome<Frontmatter>, SessionProblemCode>>;
|
|
93
|
+
ensureOperations(requiredOperations: CollectionOperation[], options?: ConnectRequestOptions): Promise<ConnectOutcome<{
|
|
92
94
|
kind: "unchanged";
|
|
93
95
|
connection: MdbaseConnection<Frontmatter>;
|
|
94
|
-
}
|
|
96
|
+
} | MdbaseAuthorizationOutcome<Frontmatter>, SessionProblemCode>>;
|
|
97
|
+
ensureCapabilities(requiredCapabilities: ApplicationCapabilityId[], options?: ConnectRequestOptions): Promise<ConnectOutcome<{
|
|
98
|
+
kind: "unchanged";
|
|
99
|
+
connection: MdbaseConnection<Frontmatter>;
|
|
100
|
+
} | MdbaseAuthorizationOutcome<Frontmatter>, SessionProblemCode>>;
|
|
101
|
+
private ensureAuthorization;
|
|
95
102
|
handleAuthorizationCallback(callbackUrl: string, options?: ConnectRequestOptions): Promise<ConnectOutcome<MdbaseConnection<Frontmatter>, SessionProblemCode>>;
|
|
96
103
|
private completeAuthorizationCallback;
|
|
97
104
|
private beginCallbackCompletion;
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
package/dist/stored-token.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ interface ReadStoredTokenOptions {
|
|
|
3
3
|
stored: string | null;
|
|
4
4
|
collectionId: string;
|
|
5
5
|
relayEncryption: "required" | "disabled";
|
|
6
|
-
invalidate(keyHandle?: string): void;
|
|
6
|
+
invalidate(keyHandle?: string, reason?: "invalid_stored_grant" | "legacy_scope_reauthorization_required"): void;
|
|
7
7
|
directCapable(token: StoredToken): boolean;
|
|
8
8
|
}
|
|
9
9
|
export declare function readStoredToken({ stored, collectionId, relayEncryption, invalidate, directCapable }: ReadStoredTokenOptions): StoredToken | null;
|
package/dist/stored-token.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { parseGrantScope, parseStored, validFileCapability, validStoredAuthority, validStoredEncryption } from "./runtime-utils.js";
|
|
1
|
+
import { isCanonicalGrantScope, parseGrantScope, parseStored, validFileCapability, validStoredAuthority, validStoredEncryption } from "./runtime-utils.js";
|
|
2
2
|
export function readStoredToken({ stored, collectionId, relayEncryption, invalidate, directCapable }) {
|
|
3
3
|
const token = parseStored(stored);
|
|
4
|
-
const reject = (keyHandle) => {
|
|
5
|
-
invalidate(typeof keyHandle === "string" ? keyHandle : undefined);
|
|
4
|
+
const reject = (keyHandle, reason = "invalid_stored_grant") => {
|
|
5
|
+
invalidate(typeof keyHandle === "string" ? keyHandle : undefined, reason);
|
|
6
6
|
return null;
|
|
7
7
|
};
|
|
8
8
|
if (!token) {
|
|
@@ -27,8 +27,12 @@ export function readStoredToken({ stored, collectionId, relayEncryption, invalid
|
|
|
27
27
|
|| (token.refreshExpiresAt !== undefined
|
|
28
28
|
&& (typeof token.refreshExpiresAt !== "number" || !Number.isFinite(token.refreshExpiresAt))))
|
|
29
29
|
return reject(token.keyHandle);
|
|
30
|
-
|
|
30
|
+
const scope = parseGrantScope(token.scope);
|
|
31
|
+
if (!scope)
|
|
31
32
|
return reject(token.keyHandle);
|
|
33
|
+
if (!isCanonicalGrantScope(scope)) {
|
|
34
|
+
return reject(token.keyHandle, "legacy_scope_reauthorization_required");
|
|
35
|
+
}
|
|
32
36
|
if (token.fileCapability && !validFileCapability(token.fileCapability)) {
|
|
33
37
|
return reject(token.keyHandle);
|
|
34
38
|
}
|
|
@@ -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/dist/token-response.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { validateGrantEncryption } from "./crypto.js";
|
|
2
2
|
import { connectError } from "./errors.js";
|
|
3
|
-
import { parseGrantScope, validAuthorityTokenResponse, validFileCapability } from "./runtime-utils.js";
|
|
3
|
+
import { isCanonicalGrantScope, parseGrantScope, validAuthorityTokenResponse, validFileCapability } from "./runtime-utils.js";
|
|
4
4
|
export function storedTokenFromResponse({ body, clientId, keyHandle, previous, retiredKeyHandles, defaultApplicationOrigin, pinConnectorIdentity }) {
|
|
5
5
|
const collectionId = body.collection_id;
|
|
6
6
|
if (typeof collectionId !== "string") {
|
|
@@ -10,6 +10,9 @@ export function storedTokenFromResponse({ body, clientId, keyHandle, previous, r
|
|
|
10
10
|
if (!scope) {
|
|
11
11
|
throw connectError("invalid_token_response", "Authorization returned no valid collection scope.");
|
|
12
12
|
}
|
|
13
|
+
if (!isCanonicalGrantScope(scope)) {
|
|
14
|
+
throw connectError("legacy_scope_reauthorization_required", "Authorization returned a legacy contract-scoped grant. Reauthorize for the entire collection.");
|
|
15
|
+
}
|
|
13
16
|
if (body.authority && !validAuthorityTokenResponse(body.authority, collectionId)) {
|
|
14
17
|
throw connectError("invalid_token_response", "Authorization returned an invalid remote authority capability.");
|
|
15
18
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mdbase-dev/connect",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.95",
|
|
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.
|
|
36
|
+
"@mdbase-dev/connect-protocol": "0.1.0-beta.95"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"esbuild": "^0.28.1",
|