@brydio/manifest 0.1.0-alpha.45 → 0.1.0-alpha.47

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brydio/manifest",
3
- "version": "0.1.0-alpha.45",
3
+ "version": "0.1.0-alpha.47",
4
4
  "description": "The shape of a Brydio app's manifest, its collections' schemas, and the checks that say what is wrong with one.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -10,7 +10,7 @@
10
10
  "url": "https://github.com/nakel-ola/brydio-sdk",
11
11
  "directory": "packages/manifest"
12
12
  },
13
- "gitHead": "0e71c4d82c5629fe245e298e46f7835dc0e8c3ba",
13
+ "gitHead": "e8cb7890ccd27170d41d30a1447ea571a255e6ce",
14
14
  "exports": {
15
15
  ".": {
16
16
  "types": "./src/index.d.ts",
package/src/schema.d.ts CHANGED
@@ -93,6 +93,9 @@ declare const secretSchema: z.ZodObject<{
93
93
  }>>;
94
94
  }, z.core.$strip>;
95
95
  export type SecretSpec = z.infer<typeof secretSchema>;
96
+ /** The `instances` rules an app may declare (CA10). */
97
+ export declare const INSTANCE_RULES: readonly ["one-per-workspace"];
98
+ export type InstanceRule = (typeof INSTANCE_RULES)[number];
96
99
  declare const extensionShape: {
97
100
  placements: z.ZodOptional<z.ZodArray<z.ZodObject<{
98
101
  key: z.ZodOptional<z.ZodString>;
@@ -198,6 +201,15 @@ declare const extensionShape: {
198
201
  install: "install";
199
202
  }>>;
200
203
  }, z.core.$strip>>>;
204
+ /**
205
+ * How many instances a workspace may keep (CA10). `"one-per-workspace"`:
206
+ * a single workspace instance, shown in the sidebar and on any project as
207
+ * a view of that project's records; never a project's or a person's own.
208
+ * Left out, as many as people make.
209
+ */
210
+ instances: z.ZodOptional<z.ZodEnum<{
211
+ "one-per-workspace": "one-per-workspace";
212
+ }>>;
201
213
  /**
202
214
  * How records move when the schema changes between versions (A3-F07).
203
215
  * Checked against the previous version when a version is published, and
@@ -244,7 +256,7 @@ export interface DataProblem {
244
256
  field?: string;
245
257
  message: string;
246
258
  }
247
- export type DataProblemCode = FieldTypeInvalid['code'] | OpenProblemCode | ConfirmProblemCode | AnonymousProblemCode | ReadersProblemCode | EditorsProblemCode | 'data_too_many_collections' | 'data_too_many_fields' | 'data_collection_name_format' | 'data_field_name_format' | 'data_field_reserved' | 'data_label_format' | 'data_label_taken' | 'data_project_field_twice' | 'data_search_unknown_field' | 'data_search_not_text' | 'grant_collection_missing' | 'grant_tool_missing' | 'custom_name_taken' | 'custom_collection_unknown' | 'custom_input_invalid' | 'placement_key_taken' | 'placement_screen_unknown' | 'placement_home_sizes' | 'placement_sizes_not_home' | 'placement_widget_sizes' | 'placement_tab_retired' | 'placement_public_shape' | 'placement_public_nothing' | 'placement_chat_card_shape' | 'grant_tool_cross_app_self' | 'placement_children_too_deep' | 'placement_create_tool_unknown' | 'placement_create_not_write' | 'secret_name_taken' | 'grant_secrets_missing';
259
+ export type DataProblemCode = FieldTypeInvalid['code'] | OpenProblemCode | ConfirmProblemCode | AnonymousProblemCode | ReadersProblemCode | EditorsProblemCode | 'data_too_many_collections' | 'data_too_many_fields' | 'data_collection_name_format' | 'data_field_name_format' | 'data_field_reserved' | 'data_label_format' | 'data_label_taken' | 'data_project_field_twice' | 'data_search_unknown_field' | 'data_search_not_text' | 'grant_collection_missing' | 'grant_tool_missing' | 'custom_name_taken' | 'custom_collection_unknown' | 'custom_input_invalid' | 'placement_key_taken' | 'placement_screen_unknown' | 'placement_home_sizes' | 'placement_sizes_not_home' | 'placement_widget_sizes' | 'placement_tab_retired' | 'placement_public_shape' | 'placement_public_nothing' | 'placement_chat_card_shape' | 'grant_tool_cross_app_self' | 'placement_children_too_deep' | 'placement_create_tool_unknown' | 'placement_create_not_write' | 'secret_name_taken' | 'grant_secrets_missing' | 'instances_needs_sidebar';
248
260
  type Additions = z.infer<z.ZodObject<typeof extensionShape>>;
249
261
  /**
250
262
  * Everything wrong with an app's additions that a type cannot say.
@@ -376,6 +388,9 @@ export declare const manifestExtensionsSchema: z.ZodObject<{
376
388
  install: "install";
377
389
  }>>;
378
390
  }, z.core.$strip>>>;
391
+ instances: z.ZodOptional<z.ZodEnum<{
392
+ "one-per-workspace": "one-per-workspace";
393
+ }>>;
379
394
  migrations: z.ZodOptional<z.ZodArray<z.ZodObject<{
380
395
  version: z.ZodString;
381
396
  steps: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -514,6 +529,9 @@ export declare const storedExtensionsSchema: z.ZodObject<{
514
529
  install: "install";
515
530
  }>>;
516
531
  }, z.core.$strip>>>;
532
+ instances: z.ZodOptional<z.ZodEnum<{
533
+ "one-per-workspace": "one-per-workspace";
534
+ }>>;
517
535
  migrations: z.ZodOptional<z.ZodArray<z.ZodObject<{
518
536
  version: z.ZodString;
519
537
  steps: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -687,6 +705,9 @@ export declare const appManifestSchema: z.ZodObject<{
687
705
  install: "install";
688
706
  }>>;
689
707
  }, z.core.$strip>>>;
708
+ instances: z.ZodOptional<z.ZodEnum<{
709
+ "one-per-workspace": "one-per-workspace";
710
+ }>>;
690
711
  migrations: z.ZodOptional<z.ZodArray<z.ZodObject<{
691
712
  version: z.ZodString;
692
713
  steps: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
@@ -864,6 +885,9 @@ export declare const newAppManifestSchema: z.ZodObject<{
864
885
  install: "install";
865
886
  }>>;
866
887
  }, z.core.$strip>>>;
888
+ instances: z.ZodOptional<z.ZodEnum<{
889
+ "one-per-workspace": "one-per-workspace";
890
+ }>>;
867
891
  migrations: z.ZodOptional<z.ZodArray<z.ZodObject<{
868
892
  version: z.ZodString;
869
893
  steps: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
package/src/schema.js CHANGED
@@ -237,6 +237,8 @@ const grantsSchema = z.object({
237
237
  collections: z.array(z.string().max(100)).max(FIELD_LIMITS.collections + 1).optional(),
238
238
  host: z.array(z.string().max(60)).max(20).optional(),
239
239
  });
240
+ /** The `instances` rules an app may declare (CA10). */
241
+ export const INSTANCE_RULES = ['one-per-workspace'];
240
242
  const extensionShape = {
241
243
  placements: z.array(placementSchema).max(20).optional(),
242
244
  data: z.record(z.string(), collectionSchema).optional(),
@@ -245,6 +247,13 @@ const extensionShape = {
245
247
  grants: grantsSchema.optional(),
246
248
  /** The secrets its handlers read (ADR-A24): names only, never values. */
247
249
  secrets: z.array(secretSchema).max(MAX_APP_SECRETS).optional(),
250
+ /**
251
+ * How many instances a workspace may keep (CA10). `"one-per-workspace"`:
252
+ * a single workspace instance, shown in the sidebar and on any project as
253
+ * a view of that project's records; never a project's or a person's own.
254
+ * Left out, as many as people make.
255
+ */
256
+ instances: z.enum(INSTANCE_RULES).optional(),
248
257
  /**
249
258
  * How records move when the schema changes between versions (A3-F07).
250
259
  * Checked against the previous version when a version is published, and
@@ -421,6 +430,7 @@ export function dataProblems(additions, options = {}) {
421
430
  }
422
431
  problems.push(...customToolProblems(additions, options));
423
432
  problems.push(...secretProblems(additions, options));
433
+ problems.push(...instanceRuleProblems(additions));
424
434
  const screens = new Set(Object.keys(additions.screens ?? {}));
425
435
  const placementKeys = new Set();
426
436
  for (const placement of additions.placements ?? []) {
@@ -644,6 +654,23 @@ export const appToolPrefix = (name) => name
644
654
  * Each secret declared once, and asked for (ADR-A24): an app that declares
645
655
  * secrets without the `secrets` host grant could never read one.
646
656
  */
657
+ /**
658
+ * One instance per workspace is added once, from the workspace sidebar
659
+ * (CA10): an app that says so and offers no sidebar item could never be
660
+ * added at all.
661
+ */
662
+ function instanceRuleProblems(additions) {
663
+ if (additions.instances !== 'one-per-workspace')
664
+ return [];
665
+ if ((additions.placements ?? []).some(one => one.kind === 'workspace-sidebar'))
666
+ return [];
667
+ return [
668
+ {
669
+ code: 'instances_needs_sidebar',
670
+ message: 'An app kept once per workspace is added from the workspace sidebar: add a "workspace-sidebar" placement.',
671
+ },
672
+ ];
673
+ }
647
674
  function secretProblems(additions, options) {
648
675
  const problems = [];
649
676
  const secrets = additions.secrets ?? [];
@@ -678,13 +705,15 @@ const refuse = (additions, ctx, options = {}) => {
678
705
  ? ['grants', 'host']
679
706
  : problem.code === 'secret_name_taken'
680
707
  ? ['secrets']
681
- : problem.code.startsWith('grant_')
682
- ? ['grants', 'collections']
683
- : problem.code.startsWith('custom_')
684
- ? ['tools', 'custom']
685
- : problem.collection
686
- ? ['data', problem.collection, ...(problem.field ? ['schema', problem.field] : [])]
687
- : [],
708
+ : problem.code === 'instances_needs_sidebar'
709
+ ? ['instances']
710
+ : problem.code.startsWith('grant_')
711
+ ? ['grants', 'collections']
712
+ : problem.code.startsWith('custom_')
713
+ ? ['tools', 'custom']
714
+ : problem.collection
715
+ ? ['data', problem.collection, ...(problem.field ? ['schema', problem.field] : [])]
716
+ : [],
688
717
  params: { code: problem.code },
689
718
  });
690
719
  }
package/src/secrets.js CHANGED
@@ -59,9 +59,20 @@ function walk(node, at, found, publicValue = () => false) {
59
59
  */
60
60
  export function secretsInJson(document, at) {
61
61
  const found = [];
62
- walk(document, at, found, path => /(?:^|\.)placements\[\d+\]\.key$/.test(path));
62
+ walk(document, at, found, path => DECLARATIONS.some(declared => declared.test(path)));
63
63
  return found;
64
64
  }
65
+ /**
66
+ * Names in an app.json that read like credentials but are declarations: a
67
+ * placement's identity, a collection's field definition (an open-schema
68
+ * companion must declare `key`), and a tool's input field. Their values are
69
+ * types and names, never secrets.
70
+ */
71
+ const DECLARATIONS = [
72
+ /(?:^|\.)placements\[\d+\]\.key$/,
73
+ /(?:^|\.)data\.[^.[\]]+\.schema\.[^.[\]]+$/,
74
+ /(?:^|\.)tools\.custom\[\d+\]\.input\.[^.[\]]+$/,
75
+ ];
65
76
  /** Every secret declared in a bundle's files, as the server finds them. */
66
77
  export function findSecrets(files, root = '') {
67
78
  const found = [];