@brydio/manifest 0.1.0-alpha.36 → 0.1.0-alpha.38
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 +2 -2
- package/src/grants.d.ts +10 -2
- package/src/grants.js +24 -2
- package/src/index.d.ts +2 -1
- package/src/index.js +2 -1
- package/src/readers.d.ts +31 -0
- package/src/readers.js +59 -0
- package/src/schema.d.ts +25 -1
- package/src/schema.js +52 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brydio/manifest",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.38",
|
|
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": "
|
|
13
|
+
"gitHead": "4b6c80dcb849195171699f16022aab1f55d6e3b6",
|
|
14
14
|
"exports": {
|
|
15
15
|
".": {
|
|
16
16
|
"types": "./src/index.d.ts",
|
package/src/grants.d.ts
CHANGED
|
@@ -6,8 +6,16 @@
|
|
|
6
6
|
* `apps/api/src/apps/manifest/grants.ts`, and the `grant_unknown` sentence in
|
|
7
7
|
* `apps/api/src/apps/publishing/app-publish.service.ts`.
|
|
8
8
|
*/
|
|
9
|
-
/**
|
|
10
|
-
|
|
9
|
+
/**
|
|
10
|
+
* The host grants with a name of their own. A connection's is `connection:<name>`.
|
|
11
|
+
*
|
|
12
|
+
* `chats` and `chat` differ: `chats` is the typed Brydio API's reach into the
|
|
13
|
+
* person's assistant conversations; `chat` is a handler's `chat.post`, the
|
|
14
|
+
* app posting one of its cards into a workspace room (FO03). `directory` is
|
|
15
|
+
* a handler's `directory.*` and a screen's `useProfile` (FO02); `webhooks`
|
|
16
|
+
* is a handler's `webhooks.send` (FO07).
|
|
17
|
+
*/
|
|
18
|
+
export declare const HOST_CAPABILITIES: readonly ["navigate", "message", "members", "projects", "files", "chats", "model", "secrets", "notify", "approvals", "chat", "directory", "webhooks"];
|
|
11
19
|
/** The older name for `HOST_CAPABILITIES`, kept for what already imports it. */
|
|
12
20
|
export declare const KNOWN_HOST_GRANTS: readonly string[];
|
|
13
21
|
/** True for a host grant Brydio knows how to honour: a named capability, or one connection. */
|
package/src/grants.js
CHANGED
|
@@ -7,8 +7,30 @@
|
|
|
7
7
|
* `apps/api/src/apps/publishing/app-publish.service.ts`.
|
|
8
8
|
*/
|
|
9
9
|
import { BUNDLE_MANIFEST } from "./bundle.js";
|
|
10
|
-
/**
|
|
11
|
-
|
|
10
|
+
/**
|
|
11
|
+
* The host grants with a name of their own. A connection's is `connection:<name>`.
|
|
12
|
+
*
|
|
13
|
+
* `chats` and `chat` differ: `chats` is the typed Brydio API's reach into the
|
|
14
|
+
* person's assistant conversations; `chat` is a handler's `chat.post`, the
|
|
15
|
+
* app posting one of its cards into a workspace room (FO03). `directory` is
|
|
16
|
+
* a handler's `directory.*` and a screen's `useProfile` (FO02); `webhooks`
|
|
17
|
+
* is a handler's `webhooks.send` (FO07).
|
|
18
|
+
*/
|
|
19
|
+
export const HOST_CAPABILITIES = [
|
|
20
|
+
'navigate',
|
|
21
|
+
'message',
|
|
22
|
+
'members',
|
|
23
|
+
'projects',
|
|
24
|
+
'files',
|
|
25
|
+
'chats',
|
|
26
|
+
'model',
|
|
27
|
+
'secrets',
|
|
28
|
+
'notify',
|
|
29
|
+
'approvals',
|
|
30
|
+
'chat',
|
|
31
|
+
'directory',
|
|
32
|
+
'webhooks',
|
|
33
|
+
];
|
|
12
34
|
/** The older name for `HOST_CAPABILITIES`, kept for what already imports it. */
|
|
13
35
|
export const KNOWN_HOST_GRANTS = HOST_CAPABILITIES;
|
|
14
36
|
const CONNECTION = /^connection:[a-z0-9][a-z0-9_-]{0,59}$/;
|
package/src/index.d.ts
CHANGED
|
@@ -7,10 +7,11 @@ export { APP_NAME_FORMAT, MANIFEST_LIMITS, SEMVER_FORMAT, baseManifestSchema, br
|
|
|
7
7
|
export { BRAND_FIELDS, BRAND_LIMITS, brandFilePath, brandPathsOf, brandProblems, brandShapeOf, isBrandPath, pngVerdict, svgColours, toolsProblem, type BrandField, type RequirementCode, type RequirementFiles, type RequirementProblem, } from './requirements.js';
|
|
8
8
|
export { BUNDLE_MANIFEST, BUNDLE_MAX_BYTES, BUNDLE_PATH_MAX_CHARS, bundleBytes, bundleHash, brandOf, bundleProblem, codeOf, isBundlePath, isScriptPath, sizeOf, type BundleFiles, type BundleProblem, type BundleProblemCode, } from './bundle.js';
|
|
9
9
|
export { COLOUR_TOKENS, coeditedFields, FIELD_LIMITS, RESERVED_FIELDS, describeType, isCoedited, isSearchable, isSortable, isStructured, parseFieldType, structuredFields, valueProblem, valueSchema, FieldTypeInvalid, type DocumentOf, type FieldKind, type FieldType, type FieldValue, type FieldsOf, } from './field-types.js';
|
|
10
|
-
export { appManifestSchema, collectionOf, collectionsOf, dataProblems, effectivePlacementKey, HOME_CARD_SIZES, labelOf, manifestExtensionsSchema, newAppManifestSchema, SIZED_PLACEMENT_KINDS, MAX_APP_SECRETS, MAX_SECRET_CHARS, PLACEMENT_KEY, SECRET_NAME, storedExtensionsSchema, type AppManifestWithData, type CollectionSpec, type DataProblem, type DataProblemCode, type DataProblemOptions, type HomeCardSize, type ManifestExtensions, type SecretSpec, } from './schema.js';
|
|
10
|
+
export { appManifestSchema, appToolPrefix, collectionOf, crossAppTool, collectionsOf, dataProblems, effectivePlacementKey, HOME_CARD_SIZES, labelOf, manifestExtensionsSchema, newAppManifestSchema, SIZED_PLACEMENT_KINDS, MAX_APP_SECRETS, MAX_SECRET_CHARS, PLACEMENT_KEY, SECRET_NAME, storedExtensionsSchema, type AppManifestWithData, type CollectionSpec, type DataProblem, type DataProblemCode, type DataProblemOptions, type HomeCardSize, type ManifestExtensions, type SecretSpec, } from './schema.js';
|
|
11
11
|
export { OPEN_LIMITS, OPEN_TYPES, choicesProblem, definitionOf, definitionTypeProblem, describeField, fieldTypeOf, hasChoices, isPlainOpenType, keyFromName, keyProblem, missingRequired, moveValue, openSchemaProblems, openSpec, openValueProblem, type Moved, type OpenField, type OpenFieldChange, type OpenFieldValue, type OpenProblem, type OpenProblemCode, type OpenSchemaFlag, type OpenType, } from './open-schema.js';
|
|
12
12
|
export { CONFIRM_LIMITS, confirmEmailProblems, confirmEmailSpec, type ConfirmEmailDeclared, type ConfirmEmailSpec, type ConfirmProblemCode, } from './confirm-email.js';
|
|
13
13
|
export { ANONYMOUS_LIMITS, ANONYMOUS_WRITER, anonymousProblems, anonymousSpec, type AnonymousDeclared, type AnonymousProblemCode, type AnonymousSpec, } from './anonymous.js';
|
|
14
|
+
export { READERS_LIMIT, readersOf, readersProblems, type ReadersProblemCode } from './readers.js';
|
|
14
15
|
export { DOCUMENT_LIMITS } from './document-limits.js';
|
|
15
16
|
export { HOST_CAPABILITIES, KNOWN_HOST_GRANTS, isHostCapability, unknownHostGrant } from './grants.js';
|
|
16
17
|
export { diffSchemas, migrationProblems, migrationSchema, migrationStepSchema, migrationsSchema, publishedMigrationProblems, schemaOf, type Migration, type MigrationCode, type MigrationProblem, type MigrationStep, type SchemaChange, } from './migrations.js';
|
package/src/index.js
CHANGED
|
@@ -7,10 +7,11 @@ export { APP_NAME_FORMAT, MANIFEST_LIMITS, SEMVER_FORMAT, baseManifestSchema, br
|
|
|
7
7
|
export { BRAND_FIELDS, BRAND_LIMITS, brandFilePath, brandPathsOf, brandProblems, brandShapeOf, isBrandPath, pngVerdict, svgColours, toolsProblem, } from "./requirements.js";
|
|
8
8
|
export { BUNDLE_MANIFEST, BUNDLE_MAX_BYTES, BUNDLE_PATH_MAX_CHARS, bundleBytes, bundleHash, brandOf, bundleProblem, codeOf, isBundlePath, isScriptPath, sizeOf, } from "./bundle.js";
|
|
9
9
|
export { COLOUR_TOKENS, coeditedFields, FIELD_LIMITS, RESERVED_FIELDS, describeType, isCoedited, isSearchable, isSortable, isStructured, parseFieldType, structuredFields, valueProblem, valueSchema, FieldTypeInvalid, } from "./field-types.js";
|
|
10
|
-
export { appManifestSchema, collectionOf, collectionsOf, dataProblems, effectivePlacementKey, HOME_CARD_SIZES, labelOf, manifestExtensionsSchema, newAppManifestSchema, SIZED_PLACEMENT_KINDS, MAX_APP_SECRETS, MAX_SECRET_CHARS, PLACEMENT_KEY, SECRET_NAME, storedExtensionsSchema, } from "./schema.js";
|
|
10
|
+
export { appManifestSchema, appToolPrefix, collectionOf, crossAppTool, collectionsOf, dataProblems, effectivePlacementKey, HOME_CARD_SIZES, labelOf, manifestExtensionsSchema, newAppManifestSchema, SIZED_PLACEMENT_KINDS, MAX_APP_SECRETS, MAX_SECRET_CHARS, PLACEMENT_KEY, SECRET_NAME, storedExtensionsSchema, } from "./schema.js";
|
|
11
11
|
export { OPEN_LIMITS, OPEN_TYPES, choicesProblem, definitionOf, definitionTypeProblem, describeField, fieldTypeOf, hasChoices, isPlainOpenType, keyFromName, keyProblem, missingRequired, moveValue, openSchemaProblems, openSpec, openValueProblem, } from "./open-schema.js";
|
|
12
12
|
export { CONFIRM_LIMITS, confirmEmailProblems, confirmEmailSpec, } from "./confirm-email.js";
|
|
13
13
|
export { ANONYMOUS_LIMITS, ANONYMOUS_WRITER, anonymousProblems, anonymousSpec, } from "./anonymous.js";
|
|
14
|
+
export { READERS_LIMIT, readersOf, readersProblems } from "./readers.js";
|
|
14
15
|
export { DOCUMENT_LIMITS } from "./document-limits.js";
|
|
15
16
|
export { HOST_CAPABILITIES, KNOWN_HOST_GRANTS, isHostCapability, unknownHostGrant } from "./grants.js";
|
|
16
17
|
export { diffSchemas, migrationProblems, migrationSchema, migrationStepSchema, migrationsSchema, publishedMigrationProblems, schemaOf, } from "./migrations.js";
|
package/src/readers.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Record readers (P16): a record only the people it
|
|
4
|
+
* names may read.
|
|
5
|
+
*
|
|
6
|
+
* An app names one of the collection's own `string[]` fields, which holds
|
|
7
|
+
* user ids. When a record's list is empty, everyone who can read the
|
|
8
|
+
* instance reads it, as before; when it names anyone, nobody else finds it —
|
|
9
|
+
* not on a screen, not through a tool, not through the assistant, not on the
|
|
10
|
+
* co-edit socket. There is no admin past it: an app that wants its admins
|
|
11
|
+
* in puts them in the list. Brydio keeps the list in plain beside the record
|
|
12
|
+
* (`app_document.readers`), so every read can be narrowed before anything
|
|
13
|
+
* is decrypted. Mirrors Brydio's `apps/api/src/apps/manifest/readers.ts`;
|
|
14
|
+
* the two change together.
|
|
15
|
+
*/
|
|
16
|
+
export declare const readersSchema: z.ZodString;
|
|
17
|
+
export type ReadersProblemCode = 'data_readers_field' | 'data_readers_anonymous';
|
|
18
|
+
/** The most people one record's list may name. */
|
|
19
|
+
export declare const READERS_LIMIT = 1000;
|
|
20
|
+
export declare function readersProblems(collection: string, declared: {
|
|
21
|
+
schema: Record<string, unknown>;
|
|
22
|
+
readers?: string;
|
|
23
|
+
anonymous?: unknown;
|
|
24
|
+
}): {
|
|
25
|
+
code: ReadersProblemCode;
|
|
26
|
+
collection: string;
|
|
27
|
+
field?: string;
|
|
28
|
+
message: string;
|
|
29
|
+
}[];
|
|
30
|
+
/** The plain list a record is kept with: its readers, or null for everyone who can read the instance. */
|
|
31
|
+
export declare function readersOf(field: string | undefined, body: Record<string, unknown>): string[] | null;
|
package/src/readers.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { FIELD_LIMITS, parseFieldType } from "./field-types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Record readers (P16): a record only the people it
|
|
5
|
+
* names may read.
|
|
6
|
+
*
|
|
7
|
+
* An app names one of the collection's own `string[]` fields, which holds
|
|
8
|
+
* user ids. When a record's list is empty, everyone who can read the
|
|
9
|
+
* instance reads it, as before; when it names anyone, nobody else finds it —
|
|
10
|
+
* not on a screen, not through a tool, not through the assistant, not on the
|
|
11
|
+
* co-edit socket. There is no admin past it: an app that wants its admins
|
|
12
|
+
* in puts them in the list. Brydio keeps the list in plain beside the record
|
|
13
|
+
* (`app_document.readers`), so every read can be narrowed before anything
|
|
14
|
+
* is decrypted. Mirrors Brydio's `apps/api/src/apps/manifest/readers.ts`;
|
|
15
|
+
* the two change together.
|
|
16
|
+
*/
|
|
17
|
+
export const readersSchema = z.string().min(1).max(FIELD_LIMITS.nameChars);
|
|
18
|
+
/** The most people one record's list may name. */
|
|
19
|
+
export const READERS_LIMIT = 1_000;
|
|
20
|
+
export function readersProblems(collection, declared) {
|
|
21
|
+
if (!declared.readers)
|
|
22
|
+
return [];
|
|
23
|
+
const problems = [];
|
|
24
|
+
const raw = declared.schema[declared.readers];
|
|
25
|
+
let listed = false;
|
|
26
|
+
try {
|
|
27
|
+
listed = raw !== undefined && parseFieldType(raw).kind === 'string[]';
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// Refused as a field type already.
|
|
31
|
+
}
|
|
32
|
+
if (!listed) {
|
|
33
|
+
problems.push({
|
|
34
|
+
code: 'data_readers_field',
|
|
35
|
+
collection,
|
|
36
|
+
field: declared.readers,
|
|
37
|
+
message: `"${collection}" names "${declared.readers}" as its readers, which must be one of its own string[] fields holding user ids.`,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
// An anonymous answer names nobody; a list of who may read it would.
|
|
41
|
+
if (declared.anonymous) {
|
|
42
|
+
problems.push({
|
|
43
|
+
code: 'data_readers_anonymous',
|
|
44
|
+
collection,
|
|
45
|
+
message: `"${collection}" can't be anonymous and name its readers.`,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
return problems;
|
|
49
|
+
}
|
|
50
|
+
/** The plain list a record is kept with: its readers, or null for everyone who can read the instance. */
|
|
51
|
+
export function readersOf(field, body) {
|
|
52
|
+
if (!field)
|
|
53
|
+
return null;
|
|
54
|
+
const value = body[field];
|
|
55
|
+
if (!Array.isArray(value))
|
|
56
|
+
return null;
|
|
57
|
+
const ids = [...new Set(value.filter((one) => typeof one === 'string' && one.length > 0))];
|
|
58
|
+
return ids.length ? ids.sort() : null;
|
|
59
|
+
}
|
package/src/schema.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { FieldTypeInvalid, type FieldType } from './field-types.js';
|
|
|
3
3
|
import { type OpenProblemCode } from './open-schema.js';
|
|
4
4
|
import { type ConfirmProblemCode } from './confirm-email.js';
|
|
5
5
|
import { type AnonymousProblemCode, type AnonymousSpec } from './anonymous.js';
|
|
6
|
+
import { type ReadersProblemCode } from './readers.js';
|
|
6
7
|
export { COLOUR_TOKENS, FIELD_LIMITS, parseFieldType, RESERVED_FIELDS, structuredFields, type FieldKind, type FieldType, } from './field-types.js';
|
|
7
8
|
/**
|
|
8
9
|
* What an app adds to `.brydio/app.json` to be more than a bundle of servers
|
|
@@ -102,6 +103,7 @@ declare const extensionShape: {
|
|
|
102
103
|
"project-sidebar": "project-sidebar";
|
|
103
104
|
"workspace-sidebar": "workspace-sidebar";
|
|
104
105
|
"public-page": "public-page";
|
|
106
|
+
"chat-card": "chat-card";
|
|
105
107
|
}>;
|
|
106
108
|
screen: z.ZodString;
|
|
107
109
|
sizes: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
@@ -162,6 +164,7 @@ declare const extensionShape: {
|
|
|
162
164
|
group: z.ZodString;
|
|
163
165
|
minimum: z.ZodOptional<z.ZodNumber>;
|
|
164
166
|
}, z.core.$strict>>;
|
|
167
|
+
readers: z.ZodOptional<z.ZodString>;
|
|
165
168
|
}, z.core.$strip>>>;
|
|
166
169
|
tools: z.ZodOptional<z.ZodObject<{
|
|
167
170
|
generated: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -240,7 +243,7 @@ export interface DataProblem {
|
|
|
240
243
|
field?: string;
|
|
241
244
|
message: string;
|
|
242
245
|
}
|
|
243
|
-
export type DataProblemCode = FieldTypeInvalid['code'] | OpenProblemCode | ConfirmProblemCode | AnonymousProblemCode | '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_children_too_deep' | 'placement_create_tool_unknown' | 'placement_create_not_write' | 'secret_name_taken' | 'grant_secrets_missing';
|
|
246
|
+
export type DataProblemCode = FieldTypeInvalid['code'] | OpenProblemCode | ConfirmProblemCode | AnonymousProblemCode | ReadersProblemCode | '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';
|
|
244
247
|
type Additions = z.infer<z.ZodObject<typeof extensionShape>>;
|
|
245
248
|
/**
|
|
246
249
|
* Everything wrong with an app's additions that a type cannot say.
|
|
@@ -256,6 +259,17 @@ export interface DataProblemOptions {
|
|
|
256
259
|
retired?: boolean;
|
|
257
260
|
}
|
|
258
261
|
export declare function dataProblems(additions: Additions, options?: DataProblemOptions): DataProblem[];
|
|
262
|
+
/**
|
|
263
|
+
* Another installed app's tool, as `grants.tools` names it (FO07): the name
|
|
264
|
+
* the assistant knows it by, `<slug>__<tool>`. Null for anything else.
|
|
265
|
+
* A handler calls it with `tools.call('<slug>__<tool>', input)`.
|
|
266
|
+
*/
|
|
267
|
+
export declare function crossAppTool(name: string): {
|
|
268
|
+
app: string;
|
|
269
|
+
tool: string;
|
|
270
|
+
} | null;
|
|
271
|
+
/** An app's name as the front of its tools' assistant names: `issue-tracker` → `issue_tracker`. */
|
|
272
|
+
export declare const appToolPrefix: (name: string) => string;
|
|
259
273
|
/** The additions alone, for code that has the rest of the manifest already. */
|
|
260
274
|
export declare const manifestExtensionsSchema: z.ZodObject<{
|
|
261
275
|
placements: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
@@ -267,6 +281,7 @@ export declare const manifestExtensionsSchema: z.ZodObject<{
|
|
|
267
281
|
"project-sidebar": "project-sidebar";
|
|
268
282
|
"workspace-sidebar": "workspace-sidebar";
|
|
269
283
|
"public-page": "public-page";
|
|
284
|
+
"chat-card": "chat-card";
|
|
270
285
|
}>;
|
|
271
286
|
screen: z.ZodString;
|
|
272
287
|
sizes: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
@@ -327,6 +342,7 @@ export declare const manifestExtensionsSchema: z.ZodObject<{
|
|
|
327
342
|
group: z.ZodString;
|
|
328
343
|
minimum: z.ZodOptional<z.ZodNumber>;
|
|
329
344
|
}, z.core.$strict>>;
|
|
345
|
+
readers: z.ZodOptional<z.ZodString>;
|
|
330
346
|
}, z.core.$strip>>>;
|
|
331
347
|
tools: z.ZodOptional<z.ZodObject<{
|
|
332
348
|
generated: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -402,6 +418,7 @@ export declare const storedExtensionsSchema: z.ZodObject<{
|
|
|
402
418
|
"project-sidebar": "project-sidebar";
|
|
403
419
|
"workspace-sidebar": "workspace-sidebar";
|
|
404
420
|
"public-page": "public-page";
|
|
421
|
+
"chat-card": "chat-card";
|
|
405
422
|
}>;
|
|
406
423
|
screen: z.ZodString;
|
|
407
424
|
sizes: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
@@ -462,6 +479,7 @@ export declare const storedExtensionsSchema: z.ZodObject<{
|
|
|
462
479
|
group: z.ZodString;
|
|
463
480
|
minimum: z.ZodOptional<z.ZodNumber>;
|
|
464
481
|
}, z.core.$strict>>;
|
|
482
|
+
readers: z.ZodOptional<z.ZodString>;
|
|
465
483
|
}, z.core.$strip>>>;
|
|
466
484
|
tools: z.ZodOptional<z.ZodObject<{
|
|
467
485
|
generated: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -572,6 +590,7 @@ export declare const appManifestSchema: z.ZodObject<{
|
|
|
572
590
|
"project-sidebar": "project-sidebar";
|
|
573
591
|
"workspace-sidebar": "workspace-sidebar";
|
|
574
592
|
"public-page": "public-page";
|
|
593
|
+
"chat-card": "chat-card";
|
|
575
594
|
}>;
|
|
576
595
|
screen: z.ZodString;
|
|
577
596
|
sizes: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
@@ -632,6 +651,7 @@ export declare const appManifestSchema: z.ZodObject<{
|
|
|
632
651
|
group: z.ZodString;
|
|
633
652
|
minimum: z.ZodOptional<z.ZodNumber>;
|
|
634
653
|
}, z.core.$strict>>;
|
|
654
|
+
readers: z.ZodOptional<z.ZodString>;
|
|
635
655
|
}, z.core.$strip>>>;
|
|
636
656
|
tools: z.ZodOptional<z.ZodObject<{
|
|
637
657
|
generated: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -746,6 +766,7 @@ export declare const newAppManifestSchema: z.ZodObject<{
|
|
|
746
766
|
"project-sidebar": "project-sidebar";
|
|
747
767
|
"workspace-sidebar": "workspace-sidebar";
|
|
748
768
|
"public-page": "public-page";
|
|
769
|
+
"chat-card": "chat-card";
|
|
749
770
|
}>;
|
|
750
771
|
screen: z.ZodString;
|
|
751
772
|
sizes: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
@@ -806,6 +827,7 @@ export declare const newAppManifestSchema: z.ZodObject<{
|
|
|
806
827
|
group: z.ZodString;
|
|
807
828
|
minimum: z.ZodOptional<z.ZodNumber>;
|
|
808
829
|
}, z.core.$strict>>;
|
|
830
|
+
readers: z.ZodOptional<z.ZodString>;
|
|
809
831
|
}, z.core.$strip>>>;
|
|
810
832
|
tools: z.ZodOptional<z.ZodObject<{
|
|
811
833
|
generated: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -898,6 +920,8 @@ export interface CollectionSpec {
|
|
|
898
920
|
definesFieldsOf?: string;
|
|
899
921
|
/** Answers kept with no writer, read only a whole group of at least `minimum` at a time (P13). */
|
|
900
922
|
anonymous?: AnonymousSpec;
|
|
923
|
+
/** The string[] field naming who alone may read a record, when it names anyone (P16). */
|
|
924
|
+
readers?: string;
|
|
901
925
|
}
|
|
902
926
|
/**
|
|
903
927
|
* The singular a collection's tools are named with: the manifest's, else the
|
package/src/schema.js
CHANGED
|
@@ -5,6 +5,7 @@ import { migrationsSchema } from "./migrations.js";
|
|
|
5
5
|
import { openSchemaProblems } from "./open-schema.js";
|
|
6
6
|
import { confirmEmailProblems, confirmEmailSchema } from "./confirm-email.js";
|
|
7
7
|
import { anonymousProblems, anonymousSchema, anonymousSpec } from "./anonymous.js";
|
|
8
|
+
import { readersProblems, readersSchema } from "./readers.js";
|
|
8
9
|
export { COLOUR_TOKENS, FIELD_LIMITS, parseFieldType, RESERVED_FIELDS, structuredFields, } from "./field-types.js";
|
|
9
10
|
/**
|
|
10
11
|
* What an app adds to `.brydio/app.json` to be more than a bundle of servers
|
|
@@ -72,8 +73,12 @@ const placementSchema = z.object({
|
|
|
72
73
|
* `children` or `settings` (`placement_public_shape`), and it reaches only
|
|
73
74
|
* collections marked `publicRead` or `publicSubmit` and tools marked
|
|
74
75
|
* `public`.
|
|
76
|
+
* `chat-card`: a screen drawn inside a chat message the app posted with
|
|
77
|
+
* a handler's `chat.post` (FO03). Nobody places it by hand, so it has no
|
|
78
|
+
* `sizes`, `children` or `settings` (`placement_chat_card_shape`); its
|
|
79
|
+
* screen sees `placement.kind === 'chat-card'` and the card's `route`.
|
|
75
80
|
*/
|
|
76
|
-
kind: z.enum(['project-tab', 'project-widget', 'project-sidebar', 'workspace-sidebar', 'home', 'public-page']),
|
|
81
|
+
kind: z.enum(['project-tab', 'project-widget', 'project-sidebar', 'workspace-sidebar', 'home', 'public-page', 'chat-card']),
|
|
77
82
|
screen: z.string().min(1).max(FIELD_LIMITS.nameChars),
|
|
78
83
|
/** For `home` and `project-widget` only, and required there: the sizes the screen draws well at. */
|
|
79
84
|
sizes: z.array(z.enum(HOME_CARD_SIZES)).max(3).optional(),
|
|
@@ -148,6 +153,8 @@ const collectionSchema = z.object({
|
|
|
148
153
|
* batch, a watch and a create naming its own writer.
|
|
149
154
|
*/
|
|
150
155
|
anonymous: anonymousSchema.optional(),
|
|
156
|
+
/** One of its string[] fields: the user ids who alone may read a record, when it names any (P16). */
|
|
157
|
+
readers: readersSchema.optional(),
|
|
151
158
|
});
|
|
152
159
|
const screenSchema = z.object({
|
|
153
160
|
/**
|
|
@@ -385,6 +392,10 @@ export function dataProblems(additions, options = {}) {
|
|
|
385
392
|
for (const [collection, declared] of collections) {
|
|
386
393
|
problems.push(...anonymousProblems(collection, declared));
|
|
387
394
|
}
|
|
395
|
+
// A collection whose records name their readers names a list of user ids to keep them in (P16).
|
|
396
|
+
for (const [collection, declared] of collections) {
|
|
397
|
+
problems.push(...readersProblems(collection, declared));
|
|
398
|
+
}
|
|
388
399
|
// What the app keeps must be what it asks to keep (A3-F06-S01): a
|
|
389
400
|
// collection the grants leave out would be data a workspace never agreed to
|
|
390
401
|
// hold, found only when the first write is refused.
|
|
@@ -454,6 +465,14 @@ export function dataProblems(additions, options = {}) {
|
|
|
454
465
|
message: `The public page "${placement.screen}" can have no children or settings: it is one screen at its own address.`,
|
|
455
466
|
});
|
|
456
467
|
}
|
|
468
|
+
// A chat card is drawn at the message's own size, in a message nobody
|
|
469
|
+
// places by hand (FO03): nothing to size, configure or list.
|
|
470
|
+
if (placement.kind === 'chat-card' && (placement.sizes?.length || placement.children || placement.settings)) {
|
|
471
|
+
problems.push({
|
|
472
|
+
code: 'placement_chat_card_shape',
|
|
473
|
+
message: `The chat card "${placement.screen}" cannot have sizes, children or settings.`,
|
|
474
|
+
});
|
|
475
|
+
}
|
|
457
476
|
}
|
|
458
477
|
// A public page with nothing public could show a visitor nothing at all.
|
|
459
478
|
if ((additions.placements ?? []).some(placement => placement.kind === 'public-page')) {
|
|
@@ -551,6 +570,20 @@ function customToolProblems(additions, options) {
|
|
|
551
570
|
}));
|
|
552
571
|
const seen = new Set();
|
|
553
572
|
const granted = additions.grants?.tools ?? [];
|
|
573
|
+
// Another app's tool, asked for by its assistant name `<slug>__<tool>`
|
|
574
|
+
// (FO07): never an unknown custom tool of this app, but naming the app
|
|
575
|
+
// itself would be a way round its own grants.
|
|
576
|
+
const name = additions.name;
|
|
577
|
+
const own = typeof name === 'string' ? appToolPrefix(name) : null;
|
|
578
|
+
for (const grant of granted) {
|
|
579
|
+
const qualified = crossAppTool(grant);
|
|
580
|
+
if (qualified && own !== null && qualified.app === own) {
|
|
581
|
+
problems.push({
|
|
582
|
+
code: 'grant_tool_cross_app_self',
|
|
583
|
+
message: `${grant} names this app's own tool: ask for ${qualified.tool} instead.`,
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
}
|
|
554
587
|
for (const tool of custom) {
|
|
555
588
|
if (generated.has(tool.name) || seen.has(tool.name)) {
|
|
556
589
|
problems.push({
|
|
@@ -587,6 +620,22 @@ function customToolProblems(additions, options) {
|
|
|
587
620
|
}
|
|
588
621
|
return problems;
|
|
589
622
|
}
|
|
623
|
+
/**
|
|
624
|
+
* Another installed app's tool, as `grants.tools` names it (FO07): the name
|
|
625
|
+
* the assistant knows it by, `<slug>__<tool>`. Null for anything else.
|
|
626
|
+
* A handler calls it with `tools.call('<slug>__<tool>', input)`.
|
|
627
|
+
*/
|
|
628
|
+
export function crossAppTool(name) {
|
|
629
|
+
const at = name.indexOf('__');
|
|
630
|
+
if (at <= 0 || at + 2 >= name.length)
|
|
631
|
+
return null;
|
|
632
|
+
return { app: name.slice(0, at), tool: name.slice(at + 2) };
|
|
633
|
+
}
|
|
634
|
+
/** An app's name as the front of its tools' assistant names: `issue-tracker` → `issue_tracker`. */
|
|
635
|
+
export const appToolPrefix = (name) => name
|
|
636
|
+
.replace(/[^A-Za-z0-9_]+/g, '_')
|
|
637
|
+
.replace(/_+/g, '_')
|
|
638
|
+
.replace(/^_+|_+$/g, '');
|
|
590
639
|
/**
|
|
591
640
|
* Each secret declared once, and asked for (ADR-A24): an app that declares
|
|
592
641
|
* secrets without the `secrets` host grant could never read one.
|
|
@@ -619,7 +668,7 @@ const refuse = (additions, ctx, options = {}) => {
|
|
|
619
668
|
ctx.addIssue({
|
|
620
669
|
code: 'custom',
|
|
621
670
|
message: problem.message,
|
|
622
|
-
path: problem.code === 'grant_tool_missing'
|
|
671
|
+
path: problem.code === 'grant_tool_missing' || problem.code === 'grant_tool_cross_app_self'
|
|
623
672
|
? ['grants', 'tools']
|
|
624
673
|
: problem.code === 'grant_secrets_missing'
|
|
625
674
|
? ['grants', 'host']
|
|
@@ -696,6 +745,7 @@ export function collectionsOf(manifest) {
|
|
|
696
745
|
: {}),
|
|
697
746
|
...(owner ? { definesFieldsOf: owner.name } : {}),
|
|
698
747
|
...(declared.anonymous ? { anonymous: anonymousSpec(declared.anonymous) } : {}),
|
|
748
|
+
...(declared.readers ? { readers: declared.readers } : {}),
|
|
699
749
|
};
|
|
700
750
|
});
|
|
701
751
|
}
|