@openbrf/plugin-sdk 1.0.0 → 1.4.0

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.
Files changed (104) hide show
  1. package/README.md +95 -0
  2. package/dist/abort-signal.d.ts +23 -0
  3. package/dist/abort-signal.d.ts.map +1 -0
  4. package/dist/abort-signal.js +2 -0
  5. package/dist/abort-signal.js.map +1 -0
  6. package/dist/action-refusal.d.ts +101 -0
  7. package/dist/action-refusal.d.ts.map +1 -0
  8. package/dist/action-refusal.js +96 -0
  9. package/dist/action-refusal.js.map +1 -0
  10. package/dist/action-schema.d.ts.map +1 -1
  11. package/dist/action-schema.js +4 -1
  12. package/dist/action-schema.js.map +1 -1
  13. package/dist/actions.d.ts +94 -6
  14. package/dist/actions.d.ts.map +1 -1
  15. package/dist/actions.js.map +1 -1
  16. package/dist/blocks.d.ts +310 -0
  17. package/dist/blocks.d.ts.map +1 -0
  18. package/dist/blocks.js +185 -0
  19. package/dist/blocks.js.map +1 -0
  20. package/dist/catalog.d.ts +1092 -41
  21. package/dist/catalog.d.ts.map +1 -1
  22. package/dist/catalog.js +472 -15
  23. package/dist/catalog.js.map +1 -1
  24. package/dist/entitlements.d.ts +117 -0
  25. package/dist/entitlements.d.ts.map +1 -0
  26. package/dist/entitlements.js +62 -0
  27. package/dist/entitlements.js.map +1 -0
  28. package/dist/extensions.d.ts +183 -0
  29. package/dist/extensions.d.ts.map +1 -0
  30. package/dist/extensions.js +67 -0
  31. package/dist/extensions.js.map +1 -0
  32. package/dist/finances.d.ts +294 -0
  33. package/dist/finances.d.ts.map +1 -0
  34. package/dist/finances.js +56 -0
  35. package/dist/finances.js.map +1 -0
  36. package/dist/findings.d.ts +92 -1
  37. package/dist/findings.d.ts.map +1 -1
  38. package/dist/findings.js +91 -0
  39. package/dist/findings.js.map +1 -1
  40. package/dist/host.d.ts +729 -4
  41. package/dist/host.d.ts.map +1 -1
  42. package/dist/host.js +54 -0
  43. package/dist/host.js.map +1 -1
  44. package/dist/imports.d.ts +104 -0
  45. package/dist/imports.d.ts.map +1 -0
  46. package/dist/imports.js +70 -0
  47. package/dist/imports.js.map +1 -0
  48. package/dist/index.d.ts +17 -7
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +15 -6
  51. package/dist/index.js.map +1 -1
  52. package/dist/localized-text.d.ts +18 -0
  53. package/dist/localized-text.d.ts.map +1 -0
  54. package/dist/localized-text.js +17 -0
  55. package/dist/localized-text.js.map +1 -0
  56. package/dist/manifest.d.ts +859 -0
  57. package/dist/manifest.d.ts.map +1 -1
  58. package/dist/manifest.js +957 -12
  59. package/dist/manifest.js.map +1 -1
  60. package/dist/package-check.d.ts.map +1 -1
  61. package/dist/package-check.js +9 -1
  62. package/dist/package-check.js.map +1 -1
  63. package/dist/permissions.d.ts +170 -11
  64. package/dist/permissions.d.ts.map +1 -1
  65. package/dist/permissions.js +210 -2
  66. package/dist/permissions.js.map +1 -1
  67. package/dist/recipients.d.ts +139 -0
  68. package/dist/recipients.d.ts.map +1 -0
  69. package/dist/recipients.js +223 -0
  70. package/dist/recipients.js.map +1 -0
  71. package/dist/records.d.ts +130 -0
  72. package/dist/records.d.ts.map +1 -0
  73. package/dist/records.js +85 -0
  74. package/dist/records.js.map +1 -0
  75. package/dist/require-capability.d.ts +37 -0
  76. package/dist/require-capability.d.ts.map +1 -0
  77. package/dist/require-capability.js +88 -0
  78. package/dist/require-capability.js.map +1 -0
  79. package/dist/settings-schema.d.ts +35 -0
  80. package/dist/settings-schema.d.ts.map +1 -1
  81. package/dist/settings-schema.js +55 -2
  82. package/dist/settings-schema.js.map +1 -1
  83. package/package.json +4 -4
  84. package/src/abort-signal.ts +22 -0
  85. package/src/action-refusal.ts +178 -0
  86. package/src/action-schema.ts +4 -1
  87. package/src/actions.ts +103 -6
  88. package/src/blocks.ts +289 -0
  89. package/src/catalog.ts +630 -72
  90. package/src/entitlements.ts +150 -0
  91. package/src/extensions.ts +212 -0
  92. package/src/finances.ts +317 -0
  93. package/src/findings.ts +94 -0
  94. package/src/host.ts +768 -4
  95. package/src/imports.ts +135 -0
  96. package/src/index.ts +209 -0
  97. package/src/localized-text.ts +19 -0
  98. package/src/manifest.ts +1231 -12
  99. package/src/package-check.ts +15 -0
  100. package/src/permissions.ts +223 -3
  101. package/src/recipients.ts +294 -0
  102. package/src/records.ts +172 -0
  103. package/src/require-capability.ts +106 -0
  104. package/src/settings-schema.ts +64 -2
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { type PluginManifestRule } from "./findings.ts";
2
3
  /**
3
4
  * The plugin manifest.
4
5
  *
@@ -142,6 +143,403 @@ export declare function declaredActionsSchema<T extends z.ZodType<{
142
143
  * second one to arrive and docs/plugin-contract.md says so.
143
144
  */
144
145
  export declare function composedActionName(pluginId: string, actionId: string): string;
146
+ /**
147
+ * The sections of the application's navigation a plugin's view may stand in.
148
+ *
149
+ * The application's own four, in the order its band shows them: the
150
+ * association and its life, the building and the apartments in it, the board's
151
+ * own desk, and the settings. A plugin names one of them rather than a section
152
+ * of its own, so a view is found where a destination of its kind already is.
153
+ */
154
+ export declare const PLUGIN_VIEW_SECTIONS: readonly ["association", "building", "board", "settings"];
155
+ export type PluginViewSection = (typeof PLUGIN_VIEW_SECTIONS)[number];
156
+ /**
157
+ * The art. 6(1) bases a plugin may declare its processing on.
158
+ *
159
+ * Four of the six. Vital interests and public task are not bases a housing
160
+ * cooperative's plugin has any business with, and a declaration naming one
161
+ * would be a claim the board then has to correct rather than a statement it can
162
+ * publish.
163
+ */
164
+ export declare const PLUGIN_LEGAL_BASES: readonly ["consent", "contract", "legalObligation", "legitimateInterest"];
165
+ export type PluginLegalBasis = (typeof PLUGIN_LEGAL_BASES)[number];
166
+ /** Whose personal data a plugin's processing concerns. */
167
+ export declare const PLUGIN_DATA_SUBJECTS: readonly ["members", "residents"];
168
+ export type PluginDataSubject = (typeof PLUGIN_DATA_SUBJECTS)[number];
169
+ /**
170
+ * What a plugin's processing of personal data is for, on what basis, for how
171
+ * long, and about whom (ADR 0035).
172
+ *
173
+ * The install seeds the plugin's row in the record of processing activities
174
+ * from it, in the association's language, and the consent screen shows it
175
+ * before anything is downloaded. It grants nothing: what a plugin may read is
176
+ * its permissions.
177
+ */
178
+ export declare const pluginProcessingSchema: z.ZodObject<{
179
+ purpose: z.ZodObject<{
180
+ sv: z.ZodString;
181
+ en: z.ZodString;
182
+ }, z.core.$strict>;
183
+ legalBasis: z.ZodEnum<{
184
+ consent: "consent";
185
+ contract: "contract";
186
+ legalObligation: "legalObligation";
187
+ legitimateInterest: "legitimateInterest";
188
+ }>;
189
+ legitimateInterest: z.ZodOptional<z.ZodObject<{
190
+ sv: z.ZodString;
191
+ en: z.ZodString;
192
+ }, z.core.$strict>>;
193
+ retention: z.ZodObject<{
194
+ sv: z.ZodString;
195
+ en: z.ZodString;
196
+ }, z.core.$strict>;
197
+ dataSubjects: z.ZodArray<z.ZodEnum<{
198
+ members: "members";
199
+ residents: "residents";
200
+ }>>;
201
+ }, z.core.$strict>;
202
+ export type PluginProcessing = z.infer<typeof pluginProcessingSchema>;
203
+ export declare const pluginViewSchema: z.ZodObject<{
204
+ module: z.ZodDefault<z.ZodString>;
205
+ titleKey: z.ZodString;
206
+ section: z.ZodOptional<z.ZodEnum<{
207
+ association: "association";
208
+ board: "board";
209
+ building: "building";
210
+ settings: "settings";
211
+ }>>;
212
+ }, z.core.$strip>;
213
+ /** The id of an extension point, unique within the plugin that declares it. */
214
+ export declare const extensionPointIdSchema: z.ZodString;
215
+ /**
216
+ * One extension point the plugin offers other plugins (ADR 0030).
217
+ *
218
+ * Declared here rather than at registration for the reason an action is: the
219
+ * board reads what the point accepts on the consent screen before anything is
220
+ * downloaded. The bundle supplies the schemas; what may cross is settled here.
221
+ */
222
+ export declare const pluginExtensionPointSchema: z.ZodObject<{
223
+ id: z.ZodString;
224
+ version: z.ZodInt;
225
+ personalData: z.ZodDefault<z.ZodArray<z.ZodEnum<{
226
+ account: "account";
227
+ apartment: "apartment";
228
+ auditTrail: "auditTrail";
229
+ email: "email";
230
+ financial: "financial";
231
+ freeText: "freeText";
232
+ health: "health";
233
+ name: "name";
234
+ personalIdentityNumber: "personalIdentityNumber";
235
+ phone: "phone";
236
+ photograph: "photograph";
237
+ postalAddress: "postalAddress";
238
+ protected: "protected";
239
+ residency: "residency";
240
+ }>>>;
241
+ }, z.core.$strip>;
242
+ /** One extension point of another plugin that this plugin extends. */
243
+ export declare const pluginExtensionSchema: z.ZodObject<{
244
+ plugin: z.ZodString;
245
+ point: z.ZodString;
246
+ version: z.ZodInt;
247
+ personalData: z.ZodDefault<z.ZodArray<z.ZodEnum<{
248
+ account: "account";
249
+ apartment: "apartment";
250
+ auditTrail: "auditTrail";
251
+ email: "email";
252
+ financial: "financial";
253
+ freeText: "freeText";
254
+ health: "health";
255
+ name: "name";
256
+ personalIdentityNumber: "personalIdentityNumber";
257
+ phone: "phone";
258
+ photograph: "photograph";
259
+ postalAddress: "postalAddress";
260
+ protected: "protected";
261
+ residency: "residency";
262
+ }>>>;
263
+ }, z.core.$strip>;
264
+ /**
265
+ * The declared extension points over a given reading of one point.
266
+ *
267
+ * The manifest reads a point as `pluginExtensionPointSchema` does; the catalog
268
+ * index reads it strictly and with the curator's title and description. Both
269
+ * keep the rule that an id appears once, which is why the list is built here.
270
+ */
271
+ export declare function declaredExtensionPointsSchema<T extends z.ZodType<{
272
+ id: string;
273
+ }>>(point: T): z.ZodArray<T>;
274
+ /**
275
+ * The declared extensions over a given reading of one, each pair of plugin and
276
+ * point once: an addon contributes once to each point it extends.
277
+ */
278
+ export declare function declaredExtensionsSchema<T extends z.ZodType<{
279
+ plugin: string;
280
+ point: string;
281
+ }>>(extension: T): z.ZodArray<T>;
282
+ /** What the rules between a plugin's extension declarations read. */
283
+ export interface ExtensionDeclarations {
284
+ readonly id: string;
285
+ readonly permissions: readonly string[];
286
+ readonly personalData: readonly string[];
287
+ readonly deviceRoutes?: readonly unknown[];
288
+ readonly extensionPoints: readonly {
289
+ readonly personalData: readonly string[];
290
+ }[];
291
+ readonly extends: readonly {
292
+ readonly plugin: string;
293
+ readonly personalData: readonly string[];
294
+ }[];
295
+ }
296
+ /**
297
+ * The rules between a plugin's extension declarations and the rest of its
298
+ * declaration (ADR 0030), as issues with the path each one is at.
299
+ *
300
+ * One function for the manifest and the catalog entry, so an entry the loader
301
+ * would refuse is never offered for consent.
302
+ */
303
+ export declare function extensionRuleIssues(declaration: ExtensionDeclarations): {
304
+ path: (string | number)[];
305
+ message: string;
306
+ rule?: PluginManifestRule;
307
+ }[];
308
+ /**
309
+ * The permissions that keep an addon and the plugin it extends apart, or an
310
+ * empty list when there are none (ADR 0030): those of whichever of the two
311
+ * does not ask for `site:blocks` that read the register or propose an import,
312
+ * when the other does.
313
+ *
314
+ * A plugin that asks for `site:blocks` reads nothing in the register and holds
315
+ * no member list, so what it publishes cannot name a resident. An extension is
316
+ * a path between two plugins in both directions: the host hands the addon the
317
+ * point's input, and the addon hands the host its output. A website host's
318
+ * points accept no personal data, but an addon that reads the register, or
319
+ * holds the member list it proposes, could still return a name in a field the
320
+ * point types as text, and only the addon's own `personalData` would say it
321
+ * does not. A website addon extending such a host could be handed names as the
322
+ * point's input, keep them, and render them in a block. So the rule reads what
323
+ * each side can reach, from the two manifests, rather than what either
324
+ * declares it hands over. Like the rule on a single plugin, it holds an honest
325
+ * pair to the website's argument rather than a hostile one.
326
+ */
327
+ export declare function extensionPermissionsRefused(addon: {
328
+ readonly permissions: readonly string[];
329
+ }, host: {
330
+ readonly permissions: readonly string[];
331
+ }): string[];
332
+ /**
333
+ * The personal data a plugin handles that keeps an addon asking for
334
+ * `site:blocks` off it, or an empty list when there is none (ADR 0030): the
335
+ * host plugin's own `personalData`, when the addon asks for `site:blocks`.
336
+ *
337
+ * The point's input is the one thing that crosses an extension and that no
338
+ * manifest declares. A host plugin that reads nothing in the register can
339
+ * still be handed names by another of its addons, and then hand them on as a
340
+ * point's input. A host plugin that handles no personal data has none to hand
341
+ * on, so a website addon extends only such a plugin.
342
+ */
343
+ export declare function extensionHostPersonalDataRefused(addon: {
344
+ readonly permissions: readonly string[];
345
+ }, host: {
346
+ readonly personalData: readonly string[];
347
+ }): string[];
348
+ /** Why a plugin's point cannot serve an addon's extension of it. */
349
+ export type ExtensionPointRefusal = {
350
+ readonly reason: "extension-point-unknown";
351
+ } | {
352
+ readonly reason: "extension-version-unsupported";
353
+ readonly supported: number;
354
+ } | {
355
+ readonly reason: "extension-personal-data-refused";
356
+ readonly categories: string[];
357
+ };
358
+ /**
359
+ * Why the host plugin's points cannot serve an addon's extension, or null when
360
+ * they can (ADR 0030): the host declares no such point, declares it at another
361
+ * major version, or the point does not accept a personal-data category the
362
+ * addon hands over.
363
+ *
364
+ * Both the loader and the catalog read this, so an index that lists an addon
365
+ * beside the plugin it extends is refused for the three refusals the loader
366
+ * would give those two declarations. What the installed packages declare is
367
+ * the loader's to read.
368
+ */
369
+ export declare function extensionPointRefusal(extension: {
370
+ readonly point: string;
371
+ readonly version: number;
372
+ readonly personalData: readonly string[];
373
+ }, hostPoints: readonly {
374
+ readonly id: string;
375
+ readonly version: number;
376
+ readonly personalData: readonly string[];
377
+ }[]): ExtensionPointRefusal | null;
378
+ /** What the rule on a group of plugins joined by extensions reads. */
379
+ export interface ExtensionGroupMember {
380
+ readonly id: string;
381
+ readonly permissions: readonly string[];
382
+ readonly extends: readonly {
383
+ readonly plugin: string;
384
+ }[];
385
+ }
386
+ /** Why an addon's group keeps it from serving. */
387
+ export interface ExtensionGroupRefusal {
388
+ /** The plugins in its group that ask for `site:blocks`. */
389
+ readonly website: string[];
390
+ /**
391
+ * The plugins in its group that read the register or propose an import,
392
+ * and so reach the members' details.
393
+ */
394
+ readonly register: string[];
395
+ }
396
+ /**
397
+ * The addons refused because the plugins joined to them by extensions hold
398
+ * both a plugin that asks for `site:blocks` and one that reads the register or
399
+ * proposes an import, by id (ADR 0030).
400
+ *
401
+ * The rule between an addon and its host plugin reads two manifests. Data
402
+ * also crosses between two addons of one host plugin, and further: an addon
403
+ * that reads the register, or holds the member list it proposes, hands the
404
+ * host plugin a name while declaring nothing, the host plugin hands it to a
405
+ * website addon as a point's input, and the website addon renders it. Neither manifest of any one pair says so.
406
+ * So the plugins joined by extensions, through one host plugin or several,
407
+ * are read as one group, and a group never holds both kinds.
408
+ *
409
+ * The answer depends only on the set of members, never on their order, so it
410
+ * is the same whichever order the loader finds the packages in. Addons give
411
+ * way in the order of `GROUP_YIELDS`, which keeps the plugin that reads the
412
+ * register or proposes an import serving and refuses the website addon beside
413
+ * it.
414
+ */
415
+ export declare function extensionGroupRefusals(members: readonly ExtensionGroupMember[]): Map<string, ExtensionGroupRefusal>;
416
+ /**
417
+ * One device route: a path under the plugin's own mount that answers to a
418
+ * device token and to nothing else (ADR 0031).
419
+ *
420
+ * Declared in the manifest because the board reads it on the install consent
421
+ * screen, and because the guard has to know which paths take no session cookie
422
+ * before any of the plugin's code has run.
423
+ */
424
+ export declare const pluginDeviceRouteSchema: z.ZodObject<{
425
+ id: z.ZodString;
426
+ path: z.ZodString;
427
+ }, z.core.$strip>;
428
+ /** At most this many device routes per plugin. */
429
+ export declare const MAX_DEVICE_ROUTES = 4;
430
+ /**
431
+ * Whether one route path is the other or lies below it, segment by segment.
432
+ *
433
+ * `screen` and `screen/pictures` overlap; `screen` and `screens` do not.
434
+ */
435
+ export declare function routePathsOverlap(left: string, right: string): boolean;
436
+ /**
437
+ * The declared device routes, as every boundary that reads them must read
438
+ * them: the manifest and the catalog entry the consent screen renders from.
439
+ *
440
+ * An id names one route, and no two routes overlap. A token is issued for one
441
+ * route by its id, and a request is matched to one route by its path, so a
442
+ * duplicated id or a path inside another route's would leave the guard two
443
+ * answers to choose from.
444
+ */
445
+ export declare function declaredDeviceRoutesSchema<T extends z.ZodType<{
446
+ id: string;
447
+ path: string;
448
+ }>>(route: T): z.ZodArray<T>;
449
+ export declare const pluginDeviceRoutesSchema: z.ZodArray<z.ZodObject<{
450
+ id: z.ZodString;
451
+ path: z.ZodString;
452
+ }, z.core.$strip>>;
453
+ /**
454
+ * The rules a declaration of device routes has to keep beside the rest of the
455
+ * manifest, for the manifest and the catalog entry alike.
456
+ *
457
+ * A device route may not overlap the OAuth protected resource, which takes a
458
+ * different token on its own branch of the guard, and a plugin with a device
459
+ * route reads nothing from the register: a device is a screen on a wall that
460
+ * anyone passing reads (ADR 0031).
461
+ */
462
+ export declare function deviceRouteRuleIssues(declaration: {
463
+ deviceRoutes: readonly {
464
+ id: string;
465
+ path: string;
466
+ }[];
467
+ oauthProtectedResource?: string | undefined;
468
+ permissions: readonly string[];
469
+ }): {
470
+ path: (string | number)[];
471
+ message: string;
472
+ rule: PluginManifestRule;
473
+ }[];
474
+ /**
475
+ * What a plugin that proposes an import declares about the data it carries,
476
+ * for the manifest and the catalog entry alike (ADR 0043).
477
+ *
478
+ * The file is the association's member list, and the plugin carries it
479
+ * whole: every name, contact detail and personal identity number, and rows
480
+ * about persons with protected personal data, which the file does not mark.
481
+ * So the board is told so on the consent screen. And no other plugin can reach
482
+ * an identity number or a protected person, so for any other plugin either
483
+ * category would describe something that cannot happen.
484
+ */
485
+ export declare function proposeImportPersonalDataIssues(declaration: {
486
+ permissions: readonly string[];
487
+ personalData: readonly string[];
488
+ }): {
489
+ message: string;
490
+ }[];
491
+ /**
492
+ * A calendar date written `YYYY-MM-DD`, and a real one: `2027-02-30` is
493
+ * refused rather than rolled over into March.
494
+ */
495
+ export declare function isCalendarDay(text: string): boolean;
496
+ /**
497
+ * Which of an issuer's keys signed a licence (ADR 0042).
498
+ *
499
+ * It selects the keys to try and asserts nothing: two plugins may declare one
500
+ * key id with different keys, and a key is known by its fingerprint.
501
+ */
502
+ export declare const licenceKeyIdSchema: z.ZodString;
503
+ /**
504
+ * An Ed25519 public key: the base64 of its 32 raw bytes, padded, and in the
505
+ * one spelling that decodes to them. The last character before the padding
506
+ * carries two bits no key has, which is what the class of letters it may be
507
+ * from says.
508
+ */
509
+ export declare const licencePublicKeySchema: z.ZodString;
510
+ /**
511
+ * A service a plugin may present the instance's licence to: an `https` origin
512
+ * as the WHATWG URL standard serialises one, so it compares exactly.
513
+ *
514
+ * A lower-case domain in punycode, a port only when it is not 443, and no
515
+ * userinfo, path, query or fragment. A host whose last label is a number is
516
+ * an IPv4 address to that standard, which serialises it otherwise, so it is
517
+ * refused rather than compared in a form no browser would print. Plain `http`
518
+ * is refused too: a presentation sent in the clear could be replayed before
519
+ * the service saw its nonce.
520
+ */
521
+ export declare const licenceAudienceSchema: z.ZodString;
522
+ /**
523
+ * The licence a paid plugin reads (ADR 0042), and whom it trusts to issue it.
524
+ *
525
+ * The keys sit here rather than in the core, so the core holds no vendor's
526
+ * key and anyone who sells a plugin can issue licences for it in the same
527
+ * format. The host verifies; a plugin reads the result through
528
+ * `host.entitlements`.
529
+ *
530
+ * Strict, because what it grants is consented to: a misspelled field read as
531
+ * absent would be a declaration the board never saw.
532
+ */
533
+ export declare const pluginLicenceSchema: z.ZodObject<{
534
+ entitlement: z.ZodString;
535
+ issuer: z.ZodObject<{
536
+ name: z.ZodString;
537
+ url: z.ZodString;
538
+ }, z.core.$strict>;
539
+ keys: z.ZodRecord<z.ZodString, z.ZodString>;
540
+ audiences: z.ZodDefault<z.ZodArray<z.ZodString>>;
541
+ }, z.core.$strict>;
542
+ export type PluginLicenceDeclaration = z.infer<typeof pluginLicenceSchema>;
145
543
  export declare const pluginManifestSchema: z.ZodObject<{
146
544
  apiVersion: z.ZodInt;
147
545
  id: z.ZodString;
@@ -150,19 +548,62 @@ export declare const pluginManifestSchema: z.ZodObject<{
150
548
  client: z.ZodOptional<z.ZodString>;
151
549
  }, z.core.$strip>;
152
550
  permissions: z.ZodDefault<z.ZodArray<z.ZodEnum<{
551
+ "addressBook:proposeImport": "addressBook:proposeImport";
153
552
  "addressBook:read": "addressBook:read";
154
553
  "addressBook:readContact": "addressBook:readContact";
554
+ "addresses:read": "addresses:read";
555
+ "bookings:readAvailability": "bookings:readAvailability";
556
+ "calendar:read": "calendar:read";
557
+ "finances:read": "finances:read";
558
+ "finances:recordDelivery": "finances:recordDelivery";
155
559
  "jobs:schedule": "jobs:schedule";
156
560
  "mail:send": "mail:send";
561
+ "media:read": "media:read";
562
+ "news:read": "news:read";
563
+ "site:blocks": "site:blocks";
157
564
  "sms:send": "sms:send";
565
+ "themes:install": "themes:install";
158
566
  }>>>;
159
567
  personalData: z.ZodDefault<z.ZodArray<z.ZodEnum<{
568
+ account: "account";
160
569
  apartment: "apartment";
570
+ auditTrail: "auditTrail";
161
571
  email: "email";
572
+ financial: "financial";
573
+ freeText: "freeText";
574
+ health: "health";
162
575
  name: "name";
576
+ personalIdentityNumber: "personalIdentityNumber";
163
577
  phone: "phone";
578
+ photograph: "photograph";
579
+ postalAddress: "postalAddress";
580
+ protected: "protected";
164
581
  residency: "residency";
165
582
  }>>>;
583
+ processing: z.ZodOptional<z.ZodObject<{
584
+ purpose: z.ZodObject<{
585
+ sv: z.ZodString;
586
+ en: z.ZodString;
587
+ }, z.core.$strict>;
588
+ legalBasis: z.ZodEnum<{
589
+ consent: "consent";
590
+ contract: "contract";
591
+ legalObligation: "legalObligation";
592
+ legitimateInterest: "legitimateInterest";
593
+ }>;
594
+ legitimateInterest: z.ZodOptional<z.ZodObject<{
595
+ sv: z.ZodString;
596
+ en: z.ZodString;
597
+ }, z.core.$strict>>;
598
+ retention: z.ZodObject<{
599
+ sv: z.ZodString;
600
+ en: z.ZodString;
601
+ }, z.core.$strict>;
602
+ dataSubjects: z.ZodArray<z.ZodEnum<{
603
+ members: "members";
604
+ residents: "residents";
605
+ }>>;
606
+ }, z.core.$strict>>;
166
607
  settingsSchema: z.ZodOptional<z.ZodObject<{
167
608
  fields: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
168
609
  key: z.ZodString;
@@ -201,11 +642,25 @@ export declare const pluginManifestSchema: z.ZodObject<{
201
642
  value: z.ZodString;
202
643
  labelKey: z.ZodString;
203
644
  }, z.core.$strip>>;
645
+ }, z.core.$strip>, z.ZodObject<{
646
+ key: z.ZodString;
647
+ labelKey: z.ZodString;
648
+ hintKey: z.ZodOptional<z.ZodString>;
649
+ required: z.ZodDefault<z.ZodBoolean>;
650
+ type: z.ZodLiteral<"secret">;
651
+ minLength: z.ZodOptional<z.ZodInt>;
652
+ maxLength: z.ZodOptional<z.ZodInt>;
204
653
  }, z.core.$strip>], "type">>;
205
654
  }, z.core.$strip>>;
206
655
  view: z.ZodOptional<z.ZodObject<{
207
656
  module: z.ZodDefault<z.ZodString>;
208
657
  titleKey: z.ZodString;
658
+ section: z.ZodOptional<z.ZodEnum<{
659
+ association: "association";
660
+ board: "board";
661
+ building: "building";
662
+ settings: "settings";
663
+ }>>;
209
664
  }, z.core.$strip>>;
210
665
  actions: z.ZodDefault<z.ZodArray<z.ZodObject<{
211
666
  id: z.ZodString;
@@ -238,10 +693,197 @@ export declare const pluginManifestSchema: z.ZodObject<{
238
693
  }>>>;
239
694
  }, z.core.$strip>>>;
240
695
  oauthProtectedResource: z.ZodOptional<z.ZodString>;
696
+ deviceRoutes: z.ZodDefault<z.ZodArray<z.ZodObject<{
697
+ id: z.ZodString;
698
+ path: z.ZodString;
699
+ }, z.core.$strip>>>;
700
+ ai: z.ZodDefault<z.ZodBoolean>;
701
+ blocks: z.ZodDefault<z.ZodArray<z.ZodObject<{
702
+ name: z.ZodString;
703
+ labelKey: z.ZodString;
704
+ icon: z.ZodDefault<z.ZodEnum<{
705
+ calendar: "calendar";
706
+ contact: "contact";
707
+ documents: "documents";
708
+ facts: "facts";
709
+ form: "form";
710
+ heading: "heading";
711
+ image: "image";
712
+ news: "news";
713
+ paragraph: "paragraph";
714
+ people: "people";
715
+ plugin: "plugin";
716
+ questions: "questions";
717
+ }>>;
718
+ category: z.ZodDefault<z.ZodEnum<{
719
+ association: "association";
720
+ media: "media";
721
+ text: "text";
722
+ }>>;
723
+ settingsSchema: z.ZodDefault<z.ZodObject<{
724
+ fields: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
725
+ key: z.ZodString;
726
+ labelKey: z.ZodString;
727
+ hintKey: z.ZodOptional<z.ZodString>;
728
+ required: z.ZodDefault<z.ZodBoolean>;
729
+ type: z.ZodLiteral<"text">;
730
+ default: z.ZodOptional<z.ZodString>;
731
+ minLength: z.ZodOptional<z.ZodInt>;
732
+ maxLength: z.ZodOptional<z.ZodInt>;
733
+ }, z.core.$strip>, z.ZodObject<{
734
+ key: z.ZodString;
735
+ labelKey: z.ZodString;
736
+ hintKey: z.ZodOptional<z.ZodString>;
737
+ required: z.ZodDefault<z.ZodBoolean>;
738
+ type: z.ZodLiteral<"number">;
739
+ default: z.ZodOptional<z.ZodNumber>;
740
+ min: z.ZodOptional<z.ZodNumber>;
741
+ max: z.ZodOptional<z.ZodNumber>;
742
+ integer: z.ZodDefault<z.ZodBoolean>;
743
+ }, z.core.$strip>, z.ZodObject<{
744
+ key: z.ZodString;
745
+ labelKey: z.ZodString;
746
+ hintKey: z.ZodOptional<z.ZodString>;
747
+ required: z.ZodDefault<z.ZodBoolean>;
748
+ type: z.ZodLiteral<"boolean">;
749
+ default: z.ZodOptional<z.ZodBoolean>;
750
+ }, z.core.$strip>, z.ZodObject<{
751
+ key: z.ZodString;
752
+ labelKey: z.ZodString;
753
+ hintKey: z.ZodOptional<z.ZodString>;
754
+ required: z.ZodDefault<z.ZodBoolean>;
755
+ type: z.ZodLiteral<"select">;
756
+ default: z.ZodOptional<z.ZodString>;
757
+ options: z.ZodArray<z.ZodObject<{
758
+ value: z.ZodString;
759
+ labelKey: z.ZodString;
760
+ }, z.core.$strip>>;
761
+ }, z.core.$strip>, z.ZodObject<{
762
+ key: z.ZodString;
763
+ labelKey: z.ZodString;
764
+ hintKey: z.ZodOptional<z.ZodString>;
765
+ required: z.ZodDefault<z.ZodBoolean>;
766
+ type: z.ZodLiteral<"secret">;
767
+ minLength: z.ZodOptional<z.ZodInt>;
768
+ maxLength: z.ZodOptional<z.ZodInt>;
769
+ }, z.core.$strip>], "type">>;
770
+ }, z.core.$strip>>;
771
+ }, z.core.$strict>>>;
772
+ extensionPoints: z.ZodDefault<z.ZodArray<z.ZodObject<{
773
+ id: z.ZodString;
774
+ version: z.ZodInt;
775
+ personalData: z.ZodDefault<z.ZodArray<z.ZodEnum<{
776
+ account: "account";
777
+ apartment: "apartment";
778
+ auditTrail: "auditTrail";
779
+ email: "email";
780
+ financial: "financial";
781
+ freeText: "freeText";
782
+ health: "health";
783
+ name: "name";
784
+ personalIdentityNumber: "personalIdentityNumber";
785
+ phone: "phone";
786
+ photograph: "photograph";
787
+ postalAddress: "postalAddress";
788
+ protected: "protected";
789
+ residency: "residency";
790
+ }>>>;
791
+ }, z.core.$strip>>>;
792
+ extends: z.ZodDefault<z.ZodArray<z.ZodObject<{
793
+ plugin: z.ZodString;
794
+ point: z.ZodString;
795
+ version: z.ZodInt;
796
+ personalData: z.ZodDefault<z.ZodArray<z.ZodEnum<{
797
+ account: "account";
798
+ apartment: "apartment";
799
+ auditTrail: "auditTrail";
800
+ email: "email";
801
+ financial: "financial";
802
+ freeText: "freeText";
803
+ health: "health";
804
+ name: "name";
805
+ personalIdentityNumber: "personalIdentityNumber";
806
+ phone: "phone";
807
+ photograph: "photograph";
808
+ postalAddress: "postalAddress";
809
+ protected: "protected";
810
+ residency: "residency";
811
+ }>>>;
812
+ }, z.core.$strip>>>;
813
+ recipients: z.ZodDefault<z.ZodArray<z.ZodObject<{
814
+ id: z.ZodString;
815
+ origin: z.ZodString;
816
+ purpose: z.ZodObject<{
817
+ sv: z.ZodString;
818
+ en: z.ZodString;
819
+ }, z.core.$strict>;
820
+ personalData: z.ZodArray<z.ZodEnum<{
821
+ account: "account";
822
+ apartment: "apartment";
823
+ auditTrail: "auditTrail";
824
+ email: "email";
825
+ financial: "financial";
826
+ freeText: "freeText";
827
+ health: "health";
828
+ name: "name";
829
+ personalIdentityNumber: "personalIdentityNumber";
830
+ phone: "phone";
831
+ photograph: "photograph";
832
+ postalAddress: "postalAddress";
833
+ protected: "protected";
834
+ residency: "residency";
835
+ }>>;
836
+ }, z.core.$strip>>>;
837
+ licence: z.ZodOptional<z.ZodObject<{
838
+ entitlement: z.ZodString;
839
+ issuer: z.ZodObject<{
840
+ name: z.ZodString;
841
+ url: z.ZodString;
842
+ }, z.core.$strict>;
843
+ keys: z.ZodRecord<z.ZodString, z.ZodString>;
844
+ audiences: z.ZodDefault<z.ZodArray<z.ZodString>>;
845
+ }, z.core.$strict>>;
846
+ released: z.ZodOptional<z.ZodString>;
241
847
  }, z.core.$strip>;
242
848
  export type PluginManifest = z.infer<typeof pluginManifestSchema>;
243
849
  export type PluginActionDeclaration = z.infer<typeof pluginActionSchema>;
244
850
  export type PluginEntry = z.infer<typeof pluginEntrySchema>;
851
+ export type PluginView = z.infer<typeof pluginViewSchema>;
852
+ export type PluginExtensionPointDeclaration = z.infer<typeof pluginExtensionPointSchema>;
853
+ export type PluginExtensionDeclaration = z.infer<typeof pluginExtensionSchema>;
854
+ export type PluginDeviceRoute = z.infer<typeof pluginDeviceRouteSchema>;
855
+ /**
856
+ * The problem with a declaration that names personal data and no processing,
857
+ * or null when there is none.
858
+ *
859
+ * Not part of the manifest schema, which the loader also reads: a plugin
860
+ * published before the field existed has to keep loading, and its row keeps
861
+ * the placeholder it was seeded with. The package check and version 2 of the
862
+ * catalog index refuse it, so nothing listed from now on lacks one.
863
+ */
864
+ export declare function missingProcessingProblem(declaration: {
865
+ personalData: readonly string[];
866
+ processing?: PluginProcessing | undefined;
867
+ }): string | null;
868
+ /**
869
+ * Whether these permissions read the address book: a plugin holding one
870
+ * processes the residents' personal data, whatever it declares.
871
+ */
872
+ export declare function readsAddressBook(permissions: readonly string[]): boolean;
873
+ /**
874
+ * The problem with a declaration that reads the address book and names no
875
+ * personal data, or null when there is none.
876
+ *
877
+ * Such a plugin would have no processing declaration and no row in the record
878
+ * of processing activities, so nobody could object to what it reads. Held
879
+ * where `missingProcessingProblem` is, and for the same reason not in the
880
+ * manifest schema: a plugin published before this rule keeps loading, and its
881
+ * install keeps a row (`readsAddressBook`).
882
+ */
883
+ export declare function undeclaredPersonalDataProblem(declaration: {
884
+ permissions: readonly string[];
885
+ personalData: readonly string[];
886
+ }): string | null;
245
887
  /**
246
888
  * Dependency fields npm installs at install time.
247
889
  *
@@ -271,19 +913,62 @@ export declare const pluginPackageSchema: z.ZodObject<{
271
913
  client: z.ZodOptional<z.ZodString>;
272
914
  }, z.core.$strip>;
273
915
  permissions: z.ZodDefault<z.ZodArray<z.ZodEnum<{
916
+ "addressBook:proposeImport": "addressBook:proposeImport";
274
917
  "addressBook:read": "addressBook:read";
275
918
  "addressBook:readContact": "addressBook:readContact";
919
+ "addresses:read": "addresses:read";
920
+ "bookings:readAvailability": "bookings:readAvailability";
921
+ "calendar:read": "calendar:read";
922
+ "finances:read": "finances:read";
923
+ "finances:recordDelivery": "finances:recordDelivery";
276
924
  "jobs:schedule": "jobs:schedule";
277
925
  "mail:send": "mail:send";
926
+ "media:read": "media:read";
927
+ "news:read": "news:read";
928
+ "site:blocks": "site:blocks";
278
929
  "sms:send": "sms:send";
930
+ "themes:install": "themes:install";
279
931
  }>>>;
280
932
  personalData: z.ZodDefault<z.ZodArray<z.ZodEnum<{
933
+ account: "account";
281
934
  apartment: "apartment";
935
+ auditTrail: "auditTrail";
282
936
  email: "email";
937
+ financial: "financial";
938
+ freeText: "freeText";
939
+ health: "health";
283
940
  name: "name";
941
+ personalIdentityNumber: "personalIdentityNumber";
284
942
  phone: "phone";
943
+ photograph: "photograph";
944
+ postalAddress: "postalAddress";
945
+ protected: "protected";
285
946
  residency: "residency";
286
947
  }>>>;
948
+ processing: z.ZodOptional<z.ZodObject<{
949
+ purpose: z.ZodObject<{
950
+ sv: z.ZodString;
951
+ en: z.ZodString;
952
+ }, z.core.$strict>;
953
+ legalBasis: z.ZodEnum<{
954
+ consent: "consent";
955
+ contract: "contract";
956
+ legalObligation: "legalObligation";
957
+ legitimateInterest: "legitimateInterest";
958
+ }>;
959
+ legitimateInterest: z.ZodOptional<z.ZodObject<{
960
+ sv: z.ZodString;
961
+ en: z.ZodString;
962
+ }, z.core.$strict>>;
963
+ retention: z.ZodObject<{
964
+ sv: z.ZodString;
965
+ en: z.ZodString;
966
+ }, z.core.$strict>;
967
+ dataSubjects: z.ZodArray<z.ZodEnum<{
968
+ members: "members";
969
+ residents: "residents";
970
+ }>>;
971
+ }, z.core.$strict>>;
287
972
  settingsSchema: z.ZodOptional<z.ZodObject<{
288
973
  fields: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
289
974
  key: z.ZodString;
@@ -322,11 +1007,25 @@ export declare const pluginPackageSchema: z.ZodObject<{
322
1007
  value: z.ZodString;
323
1008
  labelKey: z.ZodString;
324
1009
  }, z.core.$strip>>;
1010
+ }, z.core.$strip>, z.ZodObject<{
1011
+ key: z.ZodString;
1012
+ labelKey: z.ZodString;
1013
+ hintKey: z.ZodOptional<z.ZodString>;
1014
+ required: z.ZodDefault<z.ZodBoolean>;
1015
+ type: z.ZodLiteral<"secret">;
1016
+ minLength: z.ZodOptional<z.ZodInt>;
1017
+ maxLength: z.ZodOptional<z.ZodInt>;
325
1018
  }, z.core.$strip>], "type">>;
326
1019
  }, z.core.$strip>>;
327
1020
  view: z.ZodOptional<z.ZodObject<{
328
1021
  module: z.ZodDefault<z.ZodString>;
329
1022
  titleKey: z.ZodString;
1023
+ section: z.ZodOptional<z.ZodEnum<{
1024
+ association: "association";
1025
+ board: "board";
1026
+ building: "building";
1027
+ settings: "settings";
1028
+ }>>;
330
1029
  }, z.core.$strip>>;
331
1030
  actions: z.ZodDefault<z.ZodArray<z.ZodObject<{
332
1031
  id: z.ZodString;
@@ -359,6 +1058,157 @@ export declare const pluginPackageSchema: z.ZodObject<{
359
1058
  }>>>;
360
1059
  }, z.core.$strip>>>;
361
1060
  oauthProtectedResource: z.ZodOptional<z.ZodString>;
1061
+ deviceRoutes: z.ZodDefault<z.ZodArray<z.ZodObject<{
1062
+ id: z.ZodString;
1063
+ path: z.ZodString;
1064
+ }, z.core.$strip>>>;
1065
+ ai: z.ZodDefault<z.ZodBoolean>;
1066
+ blocks: z.ZodDefault<z.ZodArray<z.ZodObject<{
1067
+ name: z.ZodString;
1068
+ labelKey: z.ZodString;
1069
+ icon: z.ZodDefault<z.ZodEnum<{
1070
+ calendar: "calendar";
1071
+ contact: "contact";
1072
+ documents: "documents";
1073
+ facts: "facts";
1074
+ form: "form";
1075
+ heading: "heading";
1076
+ image: "image";
1077
+ news: "news";
1078
+ paragraph: "paragraph";
1079
+ people: "people";
1080
+ plugin: "plugin";
1081
+ questions: "questions";
1082
+ }>>;
1083
+ category: z.ZodDefault<z.ZodEnum<{
1084
+ association: "association";
1085
+ media: "media";
1086
+ text: "text";
1087
+ }>>;
1088
+ settingsSchema: z.ZodDefault<z.ZodObject<{
1089
+ fields: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
1090
+ key: z.ZodString;
1091
+ labelKey: z.ZodString;
1092
+ hintKey: z.ZodOptional<z.ZodString>;
1093
+ required: z.ZodDefault<z.ZodBoolean>;
1094
+ type: z.ZodLiteral<"text">;
1095
+ default: z.ZodOptional<z.ZodString>;
1096
+ minLength: z.ZodOptional<z.ZodInt>;
1097
+ maxLength: z.ZodOptional<z.ZodInt>;
1098
+ }, z.core.$strip>, z.ZodObject<{
1099
+ key: z.ZodString;
1100
+ labelKey: z.ZodString;
1101
+ hintKey: z.ZodOptional<z.ZodString>;
1102
+ required: z.ZodDefault<z.ZodBoolean>;
1103
+ type: z.ZodLiteral<"number">;
1104
+ default: z.ZodOptional<z.ZodNumber>;
1105
+ min: z.ZodOptional<z.ZodNumber>;
1106
+ max: z.ZodOptional<z.ZodNumber>;
1107
+ integer: z.ZodDefault<z.ZodBoolean>;
1108
+ }, z.core.$strip>, z.ZodObject<{
1109
+ key: z.ZodString;
1110
+ labelKey: z.ZodString;
1111
+ hintKey: z.ZodOptional<z.ZodString>;
1112
+ required: z.ZodDefault<z.ZodBoolean>;
1113
+ type: z.ZodLiteral<"boolean">;
1114
+ default: z.ZodOptional<z.ZodBoolean>;
1115
+ }, z.core.$strip>, z.ZodObject<{
1116
+ key: z.ZodString;
1117
+ labelKey: z.ZodString;
1118
+ hintKey: z.ZodOptional<z.ZodString>;
1119
+ required: z.ZodDefault<z.ZodBoolean>;
1120
+ type: z.ZodLiteral<"select">;
1121
+ default: z.ZodOptional<z.ZodString>;
1122
+ options: z.ZodArray<z.ZodObject<{
1123
+ value: z.ZodString;
1124
+ labelKey: z.ZodString;
1125
+ }, z.core.$strip>>;
1126
+ }, z.core.$strip>, z.ZodObject<{
1127
+ key: z.ZodString;
1128
+ labelKey: z.ZodString;
1129
+ hintKey: z.ZodOptional<z.ZodString>;
1130
+ required: z.ZodDefault<z.ZodBoolean>;
1131
+ type: z.ZodLiteral<"secret">;
1132
+ minLength: z.ZodOptional<z.ZodInt>;
1133
+ maxLength: z.ZodOptional<z.ZodInt>;
1134
+ }, z.core.$strip>], "type">>;
1135
+ }, z.core.$strip>>;
1136
+ }, z.core.$strict>>>;
1137
+ extensionPoints: z.ZodDefault<z.ZodArray<z.ZodObject<{
1138
+ id: z.ZodString;
1139
+ version: z.ZodInt;
1140
+ personalData: z.ZodDefault<z.ZodArray<z.ZodEnum<{
1141
+ account: "account";
1142
+ apartment: "apartment";
1143
+ auditTrail: "auditTrail";
1144
+ email: "email";
1145
+ financial: "financial";
1146
+ freeText: "freeText";
1147
+ health: "health";
1148
+ name: "name";
1149
+ personalIdentityNumber: "personalIdentityNumber";
1150
+ phone: "phone";
1151
+ photograph: "photograph";
1152
+ postalAddress: "postalAddress";
1153
+ protected: "protected";
1154
+ residency: "residency";
1155
+ }>>>;
1156
+ }, z.core.$strip>>>;
1157
+ extends: z.ZodDefault<z.ZodArray<z.ZodObject<{
1158
+ plugin: z.ZodString;
1159
+ point: z.ZodString;
1160
+ version: z.ZodInt;
1161
+ personalData: z.ZodDefault<z.ZodArray<z.ZodEnum<{
1162
+ account: "account";
1163
+ apartment: "apartment";
1164
+ auditTrail: "auditTrail";
1165
+ email: "email";
1166
+ financial: "financial";
1167
+ freeText: "freeText";
1168
+ health: "health";
1169
+ name: "name";
1170
+ personalIdentityNumber: "personalIdentityNumber";
1171
+ phone: "phone";
1172
+ photograph: "photograph";
1173
+ postalAddress: "postalAddress";
1174
+ protected: "protected";
1175
+ residency: "residency";
1176
+ }>>>;
1177
+ }, z.core.$strip>>>;
1178
+ recipients: z.ZodDefault<z.ZodArray<z.ZodObject<{
1179
+ id: z.ZodString;
1180
+ origin: z.ZodString;
1181
+ purpose: z.ZodObject<{
1182
+ sv: z.ZodString;
1183
+ en: z.ZodString;
1184
+ }, z.core.$strict>;
1185
+ personalData: z.ZodArray<z.ZodEnum<{
1186
+ account: "account";
1187
+ apartment: "apartment";
1188
+ auditTrail: "auditTrail";
1189
+ email: "email";
1190
+ financial: "financial";
1191
+ freeText: "freeText";
1192
+ health: "health";
1193
+ name: "name";
1194
+ personalIdentityNumber: "personalIdentityNumber";
1195
+ phone: "phone";
1196
+ photograph: "photograph";
1197
+ postalAddress: "postalAddress";
1198
+ protected: "protected";
1199
+ residency: "residency";
1200
+ }>>;
1201
+ }, z.core.$strip>>>;
1202
+ licence: z.ZodOptional<z.ZodObject<{
1203
+ entitlement: z.ZodString;
1204
+ issuer: z.ZodObject<{
1205
+ name: z.ZodString;
1206
+ url: z.ZodString;
1207
+ }, z.core.$strict>;
1208
+ keys: z.ZodRecord<z.ZodString, z.ZodString>;
1209
+ audiences: z.ZodDefault<z.ZodArray<z.ZodString>>;
1210
+ }, z.core.$strict>>;
1211
+ released: z.ZodOptional<z.ZodString>;
362
1212
  }, z.core.$strip>;
363
1213
  dependencies: z.ZodOptional<z.ZodUnknown>;
364
1214
  optionalDependencies: z.ZodOptional<z.ZodUnknown>;
@@ -370,6 +1220,15 @@ export interface ManifestParseFailure {
370
1220
  ok: false;
371
1221
  /** One line per problem, already naming the field. */
372
1222
  issues: string[];
1223
+ /**
1224
+ * The same problems, each with the code of the rule it broke when it broke
1225
+ * one a board reads in its own language (`PLUGIN_MANIFEST_RULES`), and null
1226
+ * when it is a field's own problem.
1227
+ */
1228
+ diagnostics: {
1229
+ rule: PluginManifestRule | null;
1230
+ issue: string;
1231
+ }[];
373
1232
  }
374
1233
  export type ManifestParseResult = {
375
1234
  ok: true;