@mdbase-dev/connect-protocol 0.1.0-beta.27 → 0.1.0-beta.29
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/application-authorization.d.ts +2 -3
- package/dist/application-authorization.js +5 -10
- package/dist/capabilities.d.ts +51 -0
- package/dist/capabilities.js +49 -0
- package/dist/connect-problems.generated.d.ts +53 -24
- package/dist/connect-problems.generated.js +38 -2
- package/dist/index.d.ts +8 -14
- package/dist/index.js +3 -2
- package/dist/manifest.d.ts +45 -0
- package/dist/manifest.js +355 -0
- package/package.json +10 -3
- package/schemas/connect-problem.v1.schema.json +165 -48
- package/schemas/connect-problems.v1.catalog.json +26 -3
- package/schemas/connect-protocol.v1.schema.json +3 -19
- package/schemas/mdbase-app.schema.json +60 -2
package/dist/manifest.js
ADDED
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
3
|
+
import addFormatsModule from "ajv-formats";
|
|
4
|
+
import appManifestSchema from "../schemas/mdbase-app.schema.json" with { type: "json" };
|
|
5
|
+
import { isNativeRedirectUri } from "./index.js";
|
|
6
|
+
export const APPLICATION_MANIFEST_MAX_BYTES = 524_288;
|
|
7
|
+
export class AppManifestValidationError extends Error {
|
|
8
|
+
issues;
|
|
9
|
+
constructor(issues) {
|
|
10
|
+
super(`Invalid application manifest: ${formatManifestValidationIssues(issues)}`);
|
|
11
|
+
this.issues = issues;
|
|
12
|
+
this.name = "AppManifestValidationError";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
const ajv = new Ajv2020({
|
|
16
|
+
allErrors: true,
|
|
17
|
+
strict: true,
|
|
18
|
+
// `required` may name properties declared by an enclosing oneOf branch.
|
|
19
|
+
strictRequired: false
|
|
20
|
+
});
|
|
21
|
+
const addFormats = addFormatsModule;
|
|
22
|
+
addFormats(ajv);
|
|
23
|
+
ajv.addSchema(appManifestSchema);
|
|
24
|
+
const appManifestValidator = requiredValidator(String(appManifestSchema.$id));
|
|
25
|
+
/**
|
|
26
|
+
* Validate the complete bundled application declaration accepted by Connect.
|
|
27
|
+
*
|
|
28
|
+
* This is the canonical runtime validator used by the server and developer
|
|
29
|
+
* tooling. Keep structural rules in the published JSON Schema and cross-field
|
|
30
|
+
* rules here; consumers must not maintain parallel manifest validators.
|
|
31
|
+
*/
|
|
32
|
+
export function validateAppManifest(value, options = {}) {
|
|
33
|
+
const sizeIssue = validateSerializedSize(value, options.maxBytes ?? APPLICATION_MANIFEST_MAX_BYTES);
|
|
34
|
+
if (sizeIssue)
|
|
35
|
+
return invalid([sizeIssue]);
|
|
36
|
+
const candidate = options.allowLocal ? localManifestSchemaCandidate(value) : value;
|
|
37
|
+
const schemaResult = validationResult(appManifestValidator, candidate);
|
|
38
|
+
if (!schemaResult.valid)
|
|
39
|
+
return schemaResult;
|
|
40
|
+
const issues = [
|
|
41
|
+
...validateManifestOrigins(value, options.allowLocal === true),
|
|
42
|
+
...validateCapabilityRequirements(value),
|
|
43
|
+
...validateProvisionRequirements(value)
|
|
44
|
+
];
|
|
45
|
+
return issues.length === 0 ? { valid: true, issues: [] } : invalid(issues);
|
|
46
|
+
}
|
|
47
|
+
/** Validate, normalize optional sections, and return an isolated declaration. */
|
|
48
|
+
export function parseAppManifest(value, options = {}) {
|
|
49
|
+
const result = validateAppManifest(value, options);
|
|
50
|
+
if (!result.valid)
|
|
51
|
+
throw new AppManifestValidationError(result.issues);
|
|
52
|
+
const manifest = structuredClone(value);
|
|
53
|
+
return {
|
|
54
|
+
...manifest,
|
|
55
|
+
requirements: manifest.requirements ?? { contracts: [] },
|
|
56
|
+
provisions: manifest.provisions ?? { type_packs: [] },
|
|
57
|
+
notifications: manifest.notifications ?? { criteria: [] }
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
export function formatManifestValidationIssues(issues) {
|
|
61
|
+
return issues.map((issue) => `${issue.path || "/"} ${issue.message}`).join("; ");
|
|
62
|
+
}
|
|
63
|
+
function requiredValidator(reference) {
|
|
64
|
+
const validate = ajv.getSchema(reference);
|
|
65
|
+
if (!validate)
|
|
66
|
+
throw new Error(`Canonical schema is unavailable: ${reference}`);
|
|
67
|
+
return validate;
|
|
68
|
+
}
|
|
69
|
+
function validationResult(validate, value) {
|
|
70
|
+
if (validate(value))
|
|
71
|
+
return { valid: true, issues: [] };
|
|
72
|
+
const issues = (validate.errors ?? []).map(validationIssue);
|
|
73
|
+
const specific = issues.filter((issue) => issue.keyword !== "oneOf");
|
|
74
|
+
return invalid(specific.length > 0 ? specific : issues);
|
|
75
|
+
}
|
|
76
|
+
function validationIssue(error) {
|
|
77
|
+
const params = error.params;
|
|
78
|
+
if (error.keyword === "required" && typeof params.missingProperty === "string") {
|
|
79
|
+
return {
|
|
80
|
+
path: joinPointer(error.instancePath, params.missingProperty),
|
|
81
|
+
keyword: error.keyword,
|
|
82
|
+
message: "is required",
|
|
83
|
+
params
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
if (error.keyword === "additionalProperties"
|
|
87
|
+
&& typeof params.additionalProperty === "string") {
|
|
88
|
+
return {
|
|
89
|
+
path: joinPointer(error.instancePath, params.additionalProperty),
|
|
90
|
+
keyword: error.keyword,
|
|
91
|
+
message: "is not allowed",
|
|
92
|
+
params
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
path: error.instancePath,
|
|
97
|
+
keyword: error.keyword,
|
|
98
|
+
message: error.message ?? "is invalid",
|
|
99
|
+
params
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function joinPointer(parent, child) {
|
|
103
|
+
const escaped = child.replaceAll("~", "~0").replaceAll("/", "~1");
|
|
104
|
+
return `${parent}/${escaped}`;
|
|
105
|
+
}
|
|
106
|
+
function validateSerializedSize(value, maxBytes) {
|
|
107
|
+
const jsonIssue = validateJsonValue(value, "", new WeakSet());
|
|
108
|
+
if (jsonIssue)
|
|
109
|
+
return jsonIssue;
|
|
110
|
+
try {
|
|
111
|
+
const serialized = JSON.stringify(value);
|
|
112
|
+
const bytes = new TextEncoder().encode(serialized).byteLength;
|
|
113
|
+
return bytes <= maxBytes
|
|
114
|
+
? undefined
|
|
115
|
+
: issue("/", "maxBytes", `must not exceed ${maxBytes} UTF-8 bytes`, { maxBytes, actualBytes: bytes });
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return issue("/", "json", "must be serializable as JSON");
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function validateJsonValue(value, path, ancestors) {
|
|
122
|
+
if (value === null
|
|
123
|
+
|| typeof value === "string"
|
|
124
|
+
|| typeof value === "boolean"
|
|
125
|
+
|| (typeof value === "number" && Number.isFinite(value)))
|
|
126
|
+
return undefined;
|
|
127
|
+
if (typeof value !== "object") {
|
|
128
|
+
return issue(path || "/", "json", "must be a JSON value");
|
|
129
|
+
}
|
|
130
|
+
if (ancestors.has(value)) {
|
|
131
|
+
return issue(path || "/", "json", "must not contain circular references");
|
|
132
|
+
}
|
|
133
|
+
const prototype = Object.getPrototypeOf(value);
|
|
134
|
+
if (!Array.isArray(value) && prototype !== Object.prototype && prototype !== null) {
|
|
135
|
+
return issue(path || "/", "json", "must be a plain JSON object");
|
|
136
|
+
}
|
|
137
|
+
ancestors.add(value);
|
|
138
|
+
const entries = Array.isArray(value)
|
|
139
|
+
? value.map((item, index) => [String(index), item])
|
|
140
|
+
: Object.entries(value);
|
|
141
|
+
for (const [key, child] of entries) {
|
|
142
|
+
const childIssue = validateJsonValue(child, joinPointer(path, key), ancestors);
|
|
143
|
+
if (childIssue)
|
|
144
|
+
return childIssue;
|
|
145
|
+
}
|
|
146
|
+
ancestors.delete(value);
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
function validateManifestOrigins(value, allowLocal) {
|
|
150
|
+
const manifest = asObject(value);
|
|
151
|
+
if (manifest.distribution === "portable") {
|
|
152
|
+
if (manifest.project_url === undefined) {
|
|
153
|
+
return manifest.icon === undefined
|
|
154
|
+
? []
|
|
155
|
+
: [issue("/icon", "origin", "requires a project_url on the same origin")];
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
const project = new URL(String(manifest.project_url));
|
|
159
|
+
if (project.protocol !== "https:") {
|
|
160
|
+
return [issue("/project_url", "protocol", "must use HTTPS")];
|
|
161
|
+
}
|
|
162
|
+
if (manifest.icon !== undefined
|
|
163
|
+
&& new URL(String(manifest.icon)).origin !== project.origin) {
|
|
164
|
+
return [issue("/icon", "origin", "must use the project_url origin")];
|
|
165
|
+
}
|
|
166
|
+
return [];
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return [issue("/", "url", "contains an invalid URL")];
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
try {
|
|
173
|
+
const homepage = new URL(String(manifest.homepage));
|
|
174
|
+
if (!secureOrAllowedLocal(homepage, allowLocal)) {
|
|
175
|
+
return [issue("/homepage", "protocol", "must use HTTPS (or loopback HTTP in local mode)")];
|
|
176
|
+
}
|
|
177
|
+
for (const [index, redirect] of manifest.redirect_uris.entries()) {
|
|
178
|
+
const url = new URL(String(redirect));
|
|
179
|
+
if (url.origin === homepage.origin && !secureOrAllowedLocal(url, allowLocal)) {
|
|
180
|
+
return [issue(`/redirect_uris/${index}`, "protocol", "must use HTTPS (or loopback HTTP in local mode)")];
|
|
181
|
+
}
|
|
182
|
+
const nativeAllowed = nativeRedirectMatchesApplication(url, String(manifest.id));
|
|
183
|
+
if (url.origin !== homepage.origin && !nativeAllowed) {
|
|
184
|
+
return [issue(`/redirect_uris/${index}`, "origin", "must use the homepage origin or a private-use scheme matching the application ID")];
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (manifest.icon !== undefined
|
|
188
|
+
&& new URL(String(manifest.icon)).origin !== homepage.origin) {
|
|
189
|
+
return [issue("/icon", "origin", "must use the homepage origin")];
|
|
190
|
+
}
|
|
191
|
+
return [];
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
return [issue("/", "url", "contains an invalid URL")];
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function nativeRedirectMatchesApplication(url, applicationId) {
|
|
198
|
+
const scheme = url.protocol.slice(0, -1);
|
|
199
|
+
return isNativeRedirectUri(url)
|
|
200
|
+
&& (scheme === applicationId || scheme.startsWith(`${applicationId}.`));
|
|
201
|
+
}
|
|
202
|
+
function validateProvisionRequirements(value) {
|
|
203
|
+
const manifest = asObject(value);
|
|
204
|
+
const requirements = asObject(manifest.requirements);
|
|
205
|
+
const requiredContracts = Array.isArray(requirements.contracts)
|
|
206
|
+
? requirements.contracts
|
|
207
|
+
: [];
|
|
208
|
+
const required = new Set(requiredContracts.map((contract) => {
|
|
209
|
+
const value = asObject(contract);
|
|
210
|
+
return `${value.id}@${value.version}`;
|
|
211
|
+
}));
|
|
212
|
+
const provisions = asObject(manifest.provisions);
|
|
213
|
+
const packs = Array.isArray(provisions.type_packs) ? provisions.type_packs : [];
|
|
214
|
+
const issues = [];
|
|
215
|
+
for (const [packIndex, provisionValue] of packs.entries()) {
|
|
216
|
+
const provision = asObject(provisionValue);
|
|
217
|
+
const providedContracts = Array.isArray(provision.provides)
|
|
218
|
+
? provision.provides
|
|
219
|
+
: [];
|
|
220
|
+
for (const providedValue of providedContracts) {
|
|
221
|
+
const provided = asObject(providedValue);
|
|
222
|
+
if (!required.has(`${provided.id}@${provided.version}`)) {
|
|
223
|
+
issues.push(issue(`/provisions/type_packs/${packIndex}/provides`, "contractRequirement", "may only contain contracts required by the application"));
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const packManifest = asObject(provision.manifest);
|
|
227
|
+
const declaredResources = Array.isArray(packManifest.resources)
|
|
228
|
+
? packManifest.resources.map(asObject)
|
|
229
|
+
: [];
|
|
230
|
+
const embeddedResources = Array.isArray(provision.resources)
|
|
231
|
+
? provision.resources.map(asObject)
|
|
232
|
+
: [];
|
|
233
|
+
const embedded = new Map(embeddedResources.map((resource) => [String(resource.source), resource.document]));
|
|
234
|
+
if (embedded.size !== embeddedResources.length
|
|
235
|
+
|| new Set(declaredResources.map((resource) => String(resource.source))).size
|
|
236
|
+
!== declaredResources.length
|
|
237
|
+
|| declaredResources.length !== embeddedResources.length) {
|
|
238
|
+
issues.push(issue(`/provisions/type_packs/${packIndex}/resources`, "resourceSet", "must match manifest source paths exactly"));
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
for (const [resourceIndex, resource] of declaredResources.entries()) {
|
|
242
|
+
const source = String(resource.source);
|
|
243
|
+
const document = embedded.get(source);
|
|
244
|
+
if (typeof document !== "string") {
|
|
245
|
+
issues.push(issue(`/provisions/type_packs/${packIndex}/resources`, "resourceSet", `is missing manifest source ${source}`));
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
const digest = `sha256:${createHash("sha256").update(document).digest("hex")}`;
|
|
249
|
+
if (digest !== resource.digest) {
|
|
250
|
+
issues.push(issue(`/provisions/type_packs/${packIndex}/manifest/resources/${resourceIndex}/digest`, "digest", "does not match the embedded document"));
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return issues;
|
|
255
|
+
}
|
|
256
|
+
function validateCapabilityRequirements(value) {
|
|
257
|
+
const manifest = asObject(value);
|
|
258
|
+
const requirements = asObject(manifest.requirements);
|
|
259
|
+
const capabilities = asObject(requirements.capabilities);
|
|
260
|
+
if (Object.keys(capabilities).length === 0)
|
|
261
|
+
return [];
|
|
262
|
+
const required = Array.isArray(capabilities.required)
|
|
263
|
+
? capabilities.required.map(String)
|
|
264
|
+
: [];
|
|
265
|
+
const optional = Array.isArray(capabilities.optional)
|
|
266
|
+
? capabilities.optional.map(String)
|
|
267
|
+
: [];
|
|
268
|
+
const issues = [];
|
|
269
|
+
const overlap = optional.find((capability) => required.includes(capability));
|
|
270
|
+
if (overlap) {
|
|
271
|
+
issues.push(issue("/requirements/capabilities/optional", "disjoint", `must not repeat required capability ${overlap}`));
|
|
272
|
+
}
|
|
273
|
+
const declared = new Set([...required, ...optional]);
|
|
274
|
+
const provisions = asObject(manifest.provisions);
|
|
275
|
+
if (Array.isArray(requirements.contracts)
|
|
276
|
+
&& requirements.contracts.length > 0
|
|
277
|
+
&& requirements.access !== "full_collection"
|
|
278
|
+
&& !required.includes("definitions.contracts.current")) {
|
|
279
|
+
issues.push(issue("/requirements/capabilities/required", "contractCapability", "must require definitions.contracts.current for contract-scoped requirements"));
|
|
280
|
+
}
|
|
281
|
+
if (declared.has("definitions.type-pack.apply")) {
|
|
282
|
+
if (requirements.access !== "full_collection") {
|
|
283
|
+
issues.push(issue("/requirements/access", "typePackAccess", "must be full_collection for definitions.type-pack.apply"));
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
if (Array.isArray(provisions.type_packs)
|
|
287
|
+
&& provisions.type_packs.length > 0
|
|
288
|
+
&& !required.includes("definitions.type-pack.apply")) {
|
|
289
|
+
issues.push(issue("/requirements/capabilities/required", "typePackCapability", "must require definitions.type-pack.apply when bundled type packs are declared"));
|
|
290
|
+
}
|
|
291
|
+
if (declared.has("notifications.background-delivery")
|
|
292
|
+
&& !Array.isArray(asObject(manifest.notifications).criteria)) {
|
|
293
|
+
issues.push(issue("/notifications/criteria", "notificationCapability", "must be declared for notifications.background-delivery"));
|
|
294
|
+
}
|
|
295
|
+
const fileRequirement = asObject(requirements.files);
|
|
296
|
+
const fileActions = Array.isArray(fileRequirement.actions)
|
|
297
|
+
? new Set(fileRequirement.actions.map(String))
|
|
298
|
+
: new Set();
|
|
299
|
+
for (const action of ["list", "read", "add", "replace", "move", "delete"]) {
|
|
300
|
+
const capability = `files.${action}`;
|
|
301
|
+
if (declared.has(capability) !== fileActions.has(action)) {
|
|
302
|
+
issues.push(issue("/requirements/capabilities", "fileCapability", `${capability} and requirements.files.actions.${action} must be declared together`));
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return issues;
|
|
306
|
+
}
|
|
307
|
+
function localManifestSchemaCandidate(value) {
|
|
308
|
+
const source = asObject(value);
|
|
309
|
+
if (source.distribution === "portable")
|
|
310
|
+
return value;
|
|
311
|
+
// Only URL fields need a schema-safe local substitute. A shallow copy keeps
|
|
312
|
+
// non-JSON values visible to the schema instead of silently dropping them.
|
|
313
|
+
const object = { ...source };
|
|
314
|
+
for (const field of ["homepage", "icon"]) {
|
|
315
|
+
if (typeof object[field] === "string") {
|
|
316
|
+
object[field] = schemaSafeLocalUrl(object[field]);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
if (Array.isArray(object.redirect_uris)) {
|
|
320
|
+
object.redirect_uris = object.redirect_uris.map((url) => typeof url === "string" ? schemaSafeLocalUrl(url) : url);
|
|
321
|
+
}
|
|
322
|
+
return object;
|
|
323
|
+
}
|
|
324
|
+
function schemaSafeLocalUrl(value) {
|
|
325
|
+
try {
|
|
326
|
+
const url = new URL(value);
|
|
327
|
+
if (url.protocol === "http:" && isLoopback(url.hostname))
|
|
328
|
+
url.protocol = "https:";
|
|
329
|
+
return url.href;
|
|
330
|
+
}
|
|
331
|
+
catch {
|
|
332
|
+
return value;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
function secureOrAllowedLocal(url, allowLocal) {
|
|
336
|
+
return url.protocol === "https:"
|
|
337
|
+
|| (allowLocal && url.protocol === "http:" && isLoopback(url.hostname));
|
|
338
|
+
}
|
|
339
|
+
function isLoopback(hostname) {
|
|
340
|
+
return hostname === "localhost"
|
|
341
|
+
|| hostname === "127.0.0.1"
|
|
342
|
+
|| hostname === "[::1]"
|
|
343
|
+
|| hostname === "::1";
|
|
344
|
+
}
|
|
345
|
+
function invalid(issues) {
|
|
346
|
+
return { valid: false, issues };
|
|
347
|
+
}
|
|
348
|
+
function issue(path, keyword, message, params = {}) {
|
|
349
|
+
return { path, keyword, message, params };
|
|
350
|
+
}
|
|
351
|
+
function asObject(value) {
|
|
352
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
353
|
+
? value
|
|
354
|
+
: {};
|
|
355
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mdbase-dev/connect-protocol",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.29",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -16,15 +16,22 @@
|
|
|
16
16
|
"types": "./dist/index.d.ts",
|
|
17
17
|
"import": "./dist/index.js"
|
|
18
18
|
},
|
|
19
|
+
"./manifest": {
|
|
20
|
+
"types": "./dist/manifest.d.ts",
|
|
21
|
+
"import": "./dist/manifest.js"
|
|
22
|
+
},
|
|
19
23
|
"./schemas/*": "./schemas/*"
|
|
20
24
|
},
|
|
21
25
|
"files": [
|
|
22
26
|
"dist",
|
|
23
27
|
"schemas"
|
|
24
28
|
],
|
|
25
|
-
"
|
|
29
|
+
"dependencies": {
|
|
26
30
|
"ajv": "^8.20.0",
|
|
27
|
-
"ajv-formats": "^3.0.1"
|
|
31
|
+
"ajv-formats": "^3.0.1"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/node": "^24.0.0",
|
|
28
35
|
"typescript": "^7.0.2"
|
|
29
36
|
},
|
|
30
37
|
"scripts": {
|
|
@@ -93,6 +93,46 @@
|
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
},
|
|
96
|
+
{
|
|
97
|
+
"type": "object",
|
|
98
|
+
"additionalProperties": false,
|
|
99
|
+
"required": [
|
|
100
|
+
"problem_version",
|
|
101
|
+
"code",
|
|
102
|
+
"category",
|
|
103
|
+
"recovery",
|
|
104
|
+
"message"
|
|
105
|
+
],
|
|
106
|
+
"properties": {
|
|
107
|
+
"problem_version": {
|
|
108
|
+
"const": 1
|
|
109
|
+
},
|
|
110
|
+
"code": {
|
|
111
|
+
"const": "application_identity_unavailable"
|
|
112
|
+
},
|
|
113
|
+
"category": {
|
|
114
|
+
"const": "compatibility"
|
|
115
|
+
},
|
|
116
|
+
"recovery": {
|
|
117
|
+
"const": "upgrade_application"
|
|
118
|
+
},
|
|
119
|
+
"message": {
|
|
120
|
+
"type": "string",
|
|
121
|
+
"minLength": 1
|
|
122
|
+
},
|
|
123
|
+
"operation_outcome": {
|
|
124
|
+
"enum": [
|
|
125
|
+
"not_sent",
|
|
126
|
+
"rejected",
|
|
127
|
+
"unknown"
|
|
128
|
+
]
|
|
129
|
+
},
|
|
130
|
+
"trace_id": {
|
|
131
|
+
"type": "string",
|
|
132
|
+
"minLength": 1
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
},
|
|
96
136
|
{
|
|
97
137
|
"type": "object",
|
|
98
138
|
"additionalProperties": false,
|
|
@@ -282,6 +322,46 @@
|
|
|
282
322
|
}
|
|
283
323
|
}
|
|
284
324
|
},
|
|
325
|
+
{
|
|
326
|
+
"type": "object",
|
|
327
|
+
"additionalProperties": false,
|
|
328
|
+
"required": [
|
|
329
|
+
"problem_version",
|
|
330
|
+
"code",
|
|
331
|
+
"category",
|
|
332
|
+
"recovery",
|
|
333
|
+
"message"
|
|
334
|
+
],
|
|
335
|
+
"properties": {
|
|
336
|
+
"problem_version": {
|
|
337
|
+
"const": 1
|
|
338
|
+
},
|
|
339
|
+
"code": {
|
|
340
|
+
"const": "authorization_replayed"
|
|
341
|
+
},
|
|
342
|
+
"category": {
|
|
343
|
+
"const": "integrity"
|
|
344
|
+
},
|
|
345
|
+
"recovery": {
|
|
346
|
+
"const": "reauthorize"
|
|
347
|
+
},
|
|
348
|
+
"message": {
|
|
349
|
+
"type": "string",
|
|
350
|
+
"minLength": 1
|
|
351
|
+
},
|
|
352
|
+
"operation_outcome": {
|
|
353
|
+
"enum": [
|
|
354
|
+
"not_sent",
|
|
355
|
+
"rejected",
|
|
356
|
+
"unknown"
|
|
357
|
+
]
|
|
358
|
+
},
|
|
359
|
+
"trace_id": {
|
|
360
|
+
"type": "string",
|
|
361
|
+
"minLength": 1
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
},
|
|
285
365
|
{
|
|
286
366
|
"type": "object",
|
|
287
367
|
"additionalProperties": false,
|
|
@@ -1666,46 +1746,6 @@
|
|
|
1666
1746
|
}
|
|
1667
1747
|
}
|
|
1668
1748
|
},
|
|
1669
|
-
{
|
|
1670
|
-
"type": "object",
|
|
1671
|
-
"additionalProperties": false,
|
|
1672
|
-
"required": [
|
|
1673
|
-
"problem_version",
|
|
1674
|
-
"code",
|
|
1675
|
-
"category",
|
|
1676
|
-
"recovery",
|
|
1677
|
-
"message"
|
|
1678
|
-
],
|
|
1679
|
-
"properties": {
|
|
1680
|
-
"problem_version": {
|
|
1681
|
-
"const": 1
|
|
1682
|
-
},
|
|
1683
|
-
"code": {
|
|
1684
|
-
"const": "first_contact_handler_required"
|
|
1685
|
-
},
|
|
1686
|
-
"category": {
|
|
1687
|
-
"const": "compatibility"
|
|
1688
|
-
},
|
|
1689
|
-
"recovery": {
|
|
1690
|
-
"const": "upgrade_application"
|
|
1691
|
-
},
|
|
1692
|
-
"message": {
|
|
1693
|
-
"type": "string",
|
|
1694
|
-
"minLength": 1
|
|
1695
|
-
},
|
|
1696
|
-
"operation_outcome": {
|
|
1697
|
-
"enum": [
|
|
1698
|
-
"not_sent",
|
|
1699
|
-
"rejected",
|
|
1700
|
-
"unknown"
|
|
1701
|
-
]
|
|
1702
|
-
},
|
|
1703
|
-
"trace_id": {
|
|
1704
|
-
"type": "string",
|
|
1705
|
-
"minLength": 1
|
|
1706
|
-
}
|
|
1707
|
-
}
|
|
1708
|
-
},
|
|
1709
1749
|
{
|
|
1710
1750
|
"type": "object",
|
|
1711
1751
|
"additionalProperties": false,
|
|
@@ -1831,7 +1871,7 @@
|
|
|
1831
1871
|
"const": 1
|
|
1832
1872
|
},
|
|
1833
1873
|
"code": {
|
|
1834
|
-
"const": "
|
|
1874
|
+
"const": "invalid_application_authorization"
|
|
1835
1875
|
},
|
|
1836
1876
|
"category": {
|
|
1837
1877
|
"const": "validation"
|
|
@@ -1864,14 +1904,15 @@
|
|
|
1864
1904
|
"code",
|
|
1865
1905
|
"category",
|
|
1866
1906
|
"recovery",
|
|
1867
|
-
"message"
|
|
1907
|
+
"message",
|
|
1908
|
+
"details"
|
|
1868
1909
|
],
|
|
1869
1910
|
"properties": {
|
|
1870
1911
|
"problem_version": {
|
|
1871
1912
|
"const": 1
|
|
1872
1913
|
},
|
|
1873
1914
|
"code": {
|
|
1874
|
-
"const": "
|
|
1915
|
+
"const": "invalid_application_manifest"
|
|
1875
1916
|
},
|
|
1876
1917
|
"category": {
|
|
1877
1918
|
"const": "validation"
|
|
@@ -1893,6 +1934,42 @@
|
|
|
1893
1934
|
"trace_id": {
|
|
1894
1935
|
"type": "string",
|
|
1895
1936
|
"minLength": 1
|
|
1937
|
+
},
|
|
1938
|
+
"details": {
|
|
1939
|
+
"type": "object",
|
|
1940
|
+
"additionalProperties": false,
|
|
1941
|
+
"required": [
|
|
1942
|
+
"issues"
|
|
1943
|
+
],
|
|
1944
|
+
"properties": {
|
|
1945
|
+
"issues": {
|
|
1946
|
+
"type": "array",
|
|
1947
|
+
"items": {
|
|
1948
|
+
"type": "object",
|
|
1949
|
+
"additionalProperties": false,
|
|
1950
|
+
"required": [
|
|
1951
|
+
"path",
|
|
1952
|
+
"keyword",
|
|
1953
|
+
"message",
|
|
1954
|
+
"params"
|
|
1955
|
+
],
|
|
1956
|
+
"properties": {
|
|
1957
|
+
"path": {
|
|
1958
|
+
"type": "string"
|
|
1959
|
+
},
|
|
1960
|
+
"keyword": {
|
|
1961
|
+
"type": "string"
|
|
1962
|
+
},
|
|
1963
|
+
"message": {
|
|
1964
|
+
"type": "string"
|
|
1965
|
+
},
|
|
1966
|
+
"params": {
|
|
1967
|
+
"type": "object"
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1896
1973
|
}
|
|
1897
1974
|
}
|
|
1898
1975
|
},
|
|
@@ -1911,13 +1988,13 @@
|
|
|
1911
1988
|
"const": 1
|
|
1912
1989
|
},
|
|
1913
1990
|
"code": {
|
|
1914
|
-
"const": "
|
|
1991
|
+
"const": "invalid_callback"
|
|
1915
1992
|
},
|
|
1916
1993
|
"category": {
|
|
1917
|
-
"const": "
|
|
1994
|
+
"const": "validation"
|
|
1918
1995
|
},
|
|
1919
1996
|
"recovery": {
|
|
1920
|
-
"const": "
|
|
1997
|
+
"const": "fix_request"
|
|
1921
1998
|
},
|
|
1922
1999
|
"message": {
|
|
1923
2000
|
"type": "string",
|
|
@@ -1951,7 +2028,7 @@
|
|
|
1951
2028
|
"const": 1
|
|
1952
2029
|
},
|
|
1953
2030
|
"code": {
|
|
1954
|
-
"const": "
|
|
2031
|
+
"const": "invalid_device_authorization_response"
|
|
1955
2032
|
},
|
|
1956
2033
|
"category": {
|
|
1957
2034
|
"const": "integrity"
|
|
@@ -1991,7 +2068,7 @@
|
|
|
1991
2068
|
"const": 1
|
|
1992
2069
|
},
|
|
1993
2070
|
"code": {
|
|
1994
|
-
"const": "
|
|
2071
|
+
"const": "invalid_encrypted_response"
|
|
1995
2072
|
},
|
|
1996
2073
|
"category": {
|
|
1997
2074
|
"const": "integrity"
|
|
@@ -3243,6 +3320,46 @@
|
|
|
3243
3320
|
}
|
|
3244
3321
|
}
|
|
3245
3322
|
},
|
|
3323
|
+
{
|
|
3324
|
+
"type": "object",
|
|
3325
|
+
"additionalProperties": false,
|
|
3326
|
+
"required": [
|
|
3327
|
+
"problem_version",
|
|
3328
|
+
"code",
|
|
3329
|
+
"category",
|
|
3330
|
+
"recovery",
|
|
3331
|
+
"message"
|
|
3332
|
+
],
|
|
3333
|
+
"properties": {
|
|
3334
|
+
"problem_version": {
|
|
3335
|
+
"const": 1
|
|
3336
|
+
},
|
|
3337
|
+
"code": {
|
|
3338
|
+
"const": "reconnect_required"
|
|
3339
|
+
},
|
|
3340
|
+
"category": {
|
|
3341
|
+
"const": "authorization"
|
|
3342
|
+
},
|
|
3343
|
+
"recovery": {
|
|
3344
|
+
"const": "reauthorize"
|
|
3345
|
+
},
|
|
3346
|
+
"message": {
|
|
3347
|
+
"type": "string",
|
|
3348
|
+
"minLength": 1
|
|
3349
|
+
},
|
|
3350
|
+
"operation_outcome": {
|
|
3351
|
+
"enum": [
|
|
3352
|
+
"not_sent",
|
|
3353
|
+
"rejected",
|
|
3354
|
+
"unknown"
|
|
3355
|
+
]
|
|
3356
|
+
},
|
|
3357
|
+
"trace_id": {
|
|
3358
|
+
"type": "string",
|
|
3359
|
+
"minLength": 1
|
|
3360
|
+
}
|
|
3361
|
+
}
|
|
3362
|
+
},
|
|
3246
3363
|
{
|
|
3247
3364
|
"type": "object",
|
|
3248
3365
|
"additionalProperties": false,
|