@mulmoclaude/core 3.15.0 → 4.0.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.
@@ -1,136 +0,0 @@
1
- import { AuthoredApp, AuthoredMail } from './publishManifest';
2
- /** The audiences a view may be written for. A CLOSED set: each one names a
3
- * tier with a rule behind it, so an unknown value has nowhere to be
4
- * published to and is refused before it gets there. */
5
- export declare const VIEW_AUDIENCES: readonly ["public", "member", "participant"];
6
- export type ViewAudience = (typeof VIEW_AUDIENCES)[number];
7
- /** Where each audience's documents live under `apps/{aid}`. `public` is not
8
- * here: it keeps `config/public` + `config/view`, which are already published
9
- * and already read by a deployed runtime. */
10
- export declare const VIEW_TIER: Readonly<Record<Exclude<ViewAudience, "public">, "member" | "roster">>;
11
- /** The id `public.view` normalizes to. Fixed rather than derived, so two
12
- * implementations of the same normalization cannot pick different ones. */
13
- export declare const PUBLIC_VIEW_ID = "public";
14
- /** `config` is the projection's own document in every tier (`live:config`),
15
- * so a view may not be called that — the two would be the same document. */
16
- export declare const RESERVED_VIEW_IDS: readonly string[];
17
- /** What an id may be.
18
- *
19
- * Narrow on purpose: this value is written by the author, and it becomes a
20
- * Firestore document id under a `live:` / `staged:` prefix. Excluding `:`
21
- * keeps the prefix and the id from running together; excluding `/`, `.` and
22
- * `__…__` keeps it a legal document id that addresses the path it says. */
23
- export declare const VIEW_ID_PATTERN: RegExp;
24
- /** A view as the author wrote it, once normalized: one shape, whichever of
25
- * the two declarations it came from. `where` is the path it was written at,
26
- * carried only so a refusal names the key the author can go and edit. */
27
- export interface NormalizedView {
28
- id: string;
29
- audience: ViewAudience;
30
- path: string;
31
- collections: string[];
32
- where: string;
33
- }
34
- export type NormalizedViewsResult = {
35
- ok: true;
36
- views: NormalizedView[];
37
- } | {
38
- ok: false;
39
- problems: string[];
40
- };
41
- /** The one shape everything downstream reads.
42
- *
43
- * Every caller — the publish gate, the projection, the host that writes the
44
- * documents — goes through this, so "which declaration was used" is decided
45
- * exactly once. */
46
- export declare function normalizeViews(app: AuthoredApp): NormalizedViewsResult;
47
- /** How one audience reaches one collection's records.
48
- *
49
- * The parent page builds the query from this; the view never touches
50
- * Firestore. `own` is not a filter the rules apply for the reader — an
51
- * unscoped `list` on an own-row collection is DENIED, not narrowed — so the
52
- * scope has to travel with the declaration or the page fails. */
53
- export interface ProjectedViewCollection {
54
- cid: string;
55
- scope: "all" | "own";
56
- /** `scope: "own"` — the field carrying the reader's verified address. */
57
- emailField?: string;
58
- /** `scope: "own"` — the row is the document whose id is the reader's uid. */
59
- ownDocId?: "auth.uid";
60
- }
61
- /** How a participant reaches `cid`, or null if they cannot.
62
- *
63
- * Mirrors the rules' read branches for someone holding no role:
64
- * `partRead` (the whole collection) and `ownRow` (their own record, found
65
- * by the submit declaration's `emailField` or by a uid-derived id).
66
- *
67
- * `participantRead` is a PARAMETER rather than read off the manifest, and
68
- * that is the whole point of the signature. Publish does not promote the
69
- * manifest's value: `projectPublish` overwrites `participantRead` with what
70
- * the STAGED schemas carry, so a cid added since the last deploy is in the
71
- * manifest and not in the rules. Deriving the scope from the manifest would
72
- * publish `scope: "all"` for a collection the promoted rules then deny —
73
- * and removing one gives the mirror-image false refusal. The caller passes
74
- * the set that will actually be in force. */
75
- export declare function participantScope(app: AuthoredApp, cid: string, participantRead: readonly string[]): ProjectedViewCollection | null;
76
- /** The declaration as one non-public audience may see it — the document
77
- * published at `apps/{aid}/{tier}/live:config`, and deployed at
78
- * `staged:config`.
79
- *
80
- * The roster is NOT here, and neither is anything about another member: this
81
- * is read by everyone the tier admits, which for `roster` includes every
82
- * participant. */
83
- export interface AppViewConfigDoc extends Record<string, unknown> {
84
- name?: string;
85
- views: {
86
- id: string;
87
- collections: ProjectedViewCollection[];
88
- }[];
89
- /** The submit declarations for the collections these views draw, so the page
90
- * can show what may be sent rather than discovering it from a denial. */
91
- submit: Record<string, Record<string, unknown>>;
92
- /** What this audience may CHANGE about those collections — see
93
- * {@link writeFor}. One entry per collection that has anything writable, in
94
- * the order the views declare them; absent entries mean "read only", which
95
- * is what a page with no buttons is drawn from. */
96
- write: ProjectedViewWrite[];
97
- publishedAt: number;
98
- }
99
- /** The document ids one tier uses. `live:` and `staged:` are the only two
100
- * prefixes, so a single `match` covers the projection and every view. */
101
- export declare const viewDocId: (stage: "live" | "staged", viewId: string) => string;
102
- export declare const VIEW_CONFIG_ID = "config";
103
- /** What one audience may change about one collection.
104
- *
105
- * An entry exists only where something is actually writable; a collection a
106
- * tier may only read is absent rather than present and empty. */
107
- export interface ProjectedViewWrite {
108
- cid: string;
109
- /** The field a transition moves. Without it there are no transitions. */
110
- statusField?: string;
111
- /** `{ <current status>: [<status>...] }`, for THIS audience. */
112
- transitions?: Record<string, string[]>;
113
- /** The field naming the member a row belongs to. `member` tier only. */
114
- assigneeField?: string;
115
- /** Who may write EVERY row here — the `owner` / `editor` holders. `member`
116
- * tier only, and it is what makes the tier's one shared document honest:
117
- * see {@link writersOf}. */
118
- writers?: string[];
119
- /** Who may write only the rows ASSIGNED to them — the `assignee` holders.
120
- * Present with `assigneeField`, since without one the role grants nothing.
121
- *
122
- * The assignment CANDIDATES are these two lists together, and are left to
123
- * be derived rather than published a third time: a separate list would be
124
- * one more thing that can disagree with the two the rules actually read. */
125
- rowWriters?: string[];
126
- /** `member` tier only: the rules let only a writer (or the row's own
127
- * assignee) queue mail, so a participant handed this could only be refused. */
128
- mail?: AuthoredMail;
129
- }
130
- /** What `audience` may change about `cid`, or null when the answer is nothing.
131
- *
132
- * The two audiences differ in WHICH transition table applies, in whether
133
- * assignment exists at all, and in whether the roster's answer travels with
134
- * it; they agree that the status field is the collection's, since the rules
135
- * read one field either way. */
136
- export declare function writeFor(app: AuthoredApp, audience: Exclude<ViewAudience, "public">, cid: string): ProjectedViewWrite | null;
@@ -1,58 +0,0 @@
1
- import { AuthoredApp, AuthoredSubmit } from './publishManifest';
2
- import { StagedSchemaDoc } from './publishProject';
3
- /** What publish knows about a shared collection in this repository, as far as
4
- * these checks are concerned: its cid and the schema key its records are
5
- * identified by. The whole schema is deliberately not threaded in — these
6
- * checks are about the DECLARATION, and the primary key is the one part of
7
- * the schema the declaration can contradict. */
8
- export interface PublishableCollection {
9
- cid: string;
10
- primaryKey: string;
11
- }
12
- /** Does this submit declaration bind a record to the submitter's identity?
13
- *
14
- * The condition for requiring `submitOnly`, and deliberately NOT "declares an
15
- * `audience`": `audience` appears only in the rules' public-create branch, so
16
- * an owner or editor never meets it and can add records freely. `immutable`
17
- * is the wrong condition too — a survey's responses are not immutable and
18
- * can be padded exactly the same way.
19
- *
20
- * What these four have in common is that each one makes the record MEAN "the
21
- * person who submitted it said this": a per-uid id, a per-uid+field id, a
22
- * row stamped with the submitter's verified address, or a submission
23
- * restricted to a named participant. A record created through the writer
24
- * branch carries the same shape and none of that meaning. */
25
- export declare function bindsSubmitterIdentity(submit: AuthoredSubmit): boolean;
26
- /** Everything publish refuses, as lines the author can act on.
27
- *
28
- * All of them, every time. Publish is a manual step with a human waiting on
29
- * it; stopping at the first problem turns one review into five. */
30
- export declare function publishProblems(app: AuthoredApp, collections: readonly PublishableCollection[], publisherEmail: string): string[];
31
- /** What publish will actually promote, checked as the PAIR it becomes.
32
- *
33
- * `publishProblems` reads the manifest, where `members` and `collections` sit
34
- * side by side and agree. Publish does not write that pair. It writes the
35
- * roster from the manifest and the collection configuration from what DEPLOY
36
- * staged, so the app that lands is one half of each — and no check has ever
37
- * looked at that combination.
38
- *
39
- * The sequence that gets through: deploy revision A with no `assigneeField`,
40
- * add the field AND the member in revision B, publish without redeploying.
41
- * Every manifest-level check passes on a declaration that is internally sound,
42
- * while what lands is A's field-less configuration beside B's roster — an
43
- * assignee with nothing to be compared against, refused every write, in an app
44
- * that keeps working for everybody else. That is the precise trap
45
- * `assigneeProblems` exists to prevent, reached by the one route it cannot
46
- * see.
47
- *
48
- * Separate from `publishProblems` because it needs what deploy staged, which
49
- * is a Firestore read the host makes and this package does not. It is checked
50
- * against `stagedRuleConfig` — the same function the projection uses — rather
51
- * than against a re-derivation, so the value validated is the value written.
52
- *
53
- * Only the staged half can be stale, so only that half is named and the fix is
54
- * "deploy again" rather than "fix the declaration". */
55
- export declare function promotedRoleProblems(app: AuthoredApp, staged: {
56
- cid: string;
57
- doc: StagedSchemaDoc;
58
- }[]): string[];
@@ -1,257 +0,0 @@
1
- import { z } from 'zod';
2
- /** The roles the deployed rules understand.
3
- *
4
- * Two of them are row-scoped, in opposite directions, and the pair is what
5
- * the four-way split could not express:
6
- *
7
- * `participant` — the layer that is NAMED but reads only its OWN rows (the
8
- * rows it submitted). See `readerOf` vs `listedIn`.
9
- *
10
- * `assignee` — reads EVERY row and writes only the rows ASSIGNED to it. The
11
- * stylist who approves their own bookings and not a colleague's; the marker
12
- * who grades their own students. Which rows are theirs is
13
- * `collections[cid].assigneeField`, a field on the record holding the
14
- * member's address. Reads are deliberately unscoped: a stylist needs the
15
- * whole day's schedule, and scoping the read makes the app unusable.
16
- *
17
- * The names are permanent. The deployed rules compare these strings directly
18
- * and they are written into `app.json` files people commit, so a rename is a
19
- * migration over published apps rather than an edit. */
20
- export declare const APP_ROLES: readonly ["owner", "editor", "viewer", "participant", "assignee"];
21
- /** The declarative mail queue, as the rules re-derive it: a transition of the
22
- * status field, a recipient read off the RECORD, and a fixed template. */
23
- declare const MailZ: z.ZodObject<{
24
- toField: z.ZodString;
25
- on: z.ZodRecord<z.ZodString, z.ZodObject<{
26
- from: z.ZodArray<z.ZodString>;
27
- to: z.ZodString;
28
- }, z.core.$strict>>;
29
- dataFields: z.ZodOptional<z.ZodArray<z.ZodString>>;
30
- }, z.core.$strict>;
31
- /** What the rules read out of `collections[cid]`. NOT the schema — the schema
32
- * is published beside it, untouched, for clients to render from. */
33
- declare const CollectionConfigZ: z.ZodObject<{
34
- statusField: z.ZodOptional<z.ZodString>;
35
- transitions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>;
36
- immutable: z.ZodOptional<z.ZodBoolean>;
37
- submitOnly: z.ZodOptional<z.ZodBoolean>;
38
- assigneeField: z.ZodOptional<z.ZodString>;
39
- mirrorOf: z.ZodOptional<z.ZodString>;
40
- peerVisibility: z.ZodOptional<z.ZodEnum<{
41
- public: "public";
42
- hidden: "hidden";
43
- }>>;
44
- revealGated: z.ZodOptional<z.ZodBoolean>;
45
- gatedFrom: z.ZodOptional<z.ZodString>;
46
- revealBy: z.ZodOptional<z.ZodString>;
47
- mail: z.ZodOptional<z.ZodObject<{
48
- toField: z.ZodString;
49
- on: z.ZodRecord<z.ZodString, z.ZodObject<{
50
- from: z.ZodArray<z.ZodString>;
51
- to: z.ZodString;
52
- }, z.core.$strict>>;
53
- dataFields: z.ZodOptional<z.ZodArray<z.ZodString>>;
54
- }, z.core.$strict>>;
55
- aggregate: z.ZodOptional<z.ZodObject<{
56
- by: z.ZodArray<z.ZodString>;
57
- }, z.core.$strict>>;
58
- }, z.core.$strict>;
59
- declare const SubmitZ: z.ZodObject<{
60
- auth: z.ZodEnum<{
61
- none: "none";
62
- anonymous: "anonymous";
63
- verifiedEmail: "verifiedEmail";
64
- }>;
65
- emailField: z.ZodOptional<z.ZodString>;
66
- createFields: z.ZodArray<z.ZodString>;
67
- initialStatus: z.ZodOptional<z.ZodString>;
68
- idFrom: z.ZodOptional<z.ZodEnum<{
69
- field: "field";
70
- auto: "auto";
71
- "auth.uid": "auth.uid";
72
- "auth.uid+field": "auth.uid+field";
73
- }>>;
74
- idField: z.ZodOptional<z.ZodString>;
75
- idIn: z.ZodOptional<z.ZodObject<{
76
- collection: z.ZodString;
77
- where: z.ZodOptional<z.ZodObject<{
78
- field: z.ZodString;
79
- equals: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>;
80
- }, z.core.$strict>>;
81
- }, z.core.$strict>>;
82
- mirror: z.ZodOptional<z.ZodString>;
83
- validate: z.ZodOptional<z.ZodObject<{
84
- required: z.ZodOptional<z.ZodArray<z.ZodString>>;
85
- keyFields: z.ZodOptional<z.ZodArray<z.ZodObject<{
86
- field: z.ZodString;
87
- values: z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
88
- }, z.core.$strict>>>;
89
- }, z.core.$strict>>;
90
- window: z.ZodOptional<z.ZodObject<{
91
- from: z.ZodOptional<z.ZodISODateTime>;
92
- until: z.ZodOptional<z.ZodISODateTime>;
93
- fromField: z.ZodOptional<z.ZodObject<{
94
- ref: z.ZodString;
95
- collection: z.ZodString;
96
- field: z.ZodString;
97
- }, z.core.$strict>>;
98
- untilField: z.ZodOptional<z.ZodObject<{
99
- ref: z.ZodString;
100
- collection: z.ZodString;
101
- field: z.ZodString;
102
- }, z.core.$strict>>;
103
- }, z.core.$strict>>;
104
- stampField: z.ZodOptional<z.ZodString>;
105
- selfUpdate: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>;
106
- selfTransitions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>;
107
- finalize: z.ZodOptional<z.ZodBoolean>;
108
- audience: z.ZodOptional<z.ZodLiteral<"participant">>;
109
- gateOn: z.ZodOptional<z.ZodObject<{
110
- phase: z.ZodString;
111
- match: z.ZodString;
112
- }, z.core.$strict>>;
113
- }, z.core.$strict>;
114
- /** The whole authored declaration.
115
- *
116
- * `owner` is accepted but is NOT the published value — publish stamps the
117
- * publisher's uid (or carries the existing one forward, which is what the
118
- * rules require on update) and refuses a declaration that disagrees. It is
119
- * accepted rather than banned because the sample app.json in the design note
120
- * shows it, and a hard refusal on a key the samples contain would be a worse
121
- * first experience than a message naming the mismatch. */
122
- export declare const AuthoredAppZ: z.ZodObject<{
123
- aid: z.ZodString;
124
- name: z.ZodOptional<z.ZodString>;
125
- slug: z.ZodOptional<z.ZodString>;
126
- aidEnv: z.ZodOptional<z.ZodString>;
127
- owner: z.ZodOptional<z.ZodString>;
128
- members: z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodUnion<readonly [z.ZodLiteral<"*">, z.ZodString]>, z.ZodEnum<{
129
- participant: "participant";
130
- owner: "owner";
131
- editor: "editor";
132
- viewer: "viewer";
133
- assignee: "assignee";
134
- }>>>;
135
- collections: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
136
- statusField: z.ZodOptional<z.ZodString>;
137
- transitions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>;
138
- immutable: z.ZodOptional<z.ZodBoolean>;
139
- submitOnly: z.ZodOptional<z.ZodBoolean>;
140
- assigneeField: z.ZodOptional<z.ZodString>;
141
- mirrorOf: z.ZodOptional<z.ZodString>;
142
- peerVisibility: z.ZodOptional<z.ZodEnum<{
143
- public: "public";
144
- hidden: "hidden";
145
- }>>;
146
- revealGated: z.ZodOptional<z.ZodBoolean>;
147
- gatedFrom: z.ZodOptional<z.ZodString>;
148
- revealBy: z.ZodOptional<z.ZodString>;
149
- mail: z.ZodOptional<z.ZodObject<{
150
- toField: z.ZodString;
151
- on: z.ZodRecord<z.ZodString, z.ZodObject<{
152
- from: z.ZodArray<z.ZodString>;
153
- to: z.ZodString;
154
- }, z.core.$strict>>;
155
- dataFields: z.ZodOptional<z.ZodArray<z.ZodString>>;
156
- }, z.core.$strict>>;
157
- aggregate: z.ZodOptional<z.ZodObject<{
158
- by: z.ZodArray<z.ZodString>;
159
- }, z.core.$strict>>;
160
- }, z.core.$strict>>>;
161
- participantRead: z.ZodOptional<z.ZodArray<z.ZodString>>;
162
- public: z.ZodOptional<z.ZodObject<{
163
- enabled: z.ZodOptional<z.ZodBoolean>;
164
- read: z.ZodOptional<z.ZodArray<z.ZodString>>;
165
- view: z.ZodOptional<z.ZodObject<{
166
- path: z.ZodString;
167
- collections: z.ZodArray<z.ZodString>;
168
- }, z.core.$strict>>;
169
- submit: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
170
- auth: z.ZodEnum<{
171
- none: "none";
172
- anonymous: "anonymous";
173
- verifiedEmail: "verifiedEmail";
174
- }>;
175
- emailField: z.ZodOptional<z.ZodString>;
176
- createFields: z.ZodArray<z.ZodString>;
177
- initialStatus: z.ZodOptional<z.ZodString>;
178
- idFrom: z.ZodOptional<z.ZodEnum<{
179
- field: "field";
180
- auto: "auto";
181
- "auth.uid": "auth.uid";
182
- "auth.uid+field": "auth.uid+field";
183
- }>>;
184
- idField: z.ZodOptional<z.ZodString>;
185
- idIn: z.ZodOptional<z.ZodObject<{
186
- collection: z.ZodString;
187
- where: z.ZodOptional<z.ZodObject<{
188
- field: z.ZodString;
189
- equals: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>;
190
- }, z.core.$strict>>;
191
- }, z.core.$strict>>;
192
- mirror: z.ZodOptional<z.ZodString>;
193
- validate: z.ZodOptional<z.ZodObject<{
194
- required: z.ZodOptional<z.ZodArray<z.ZodString>>;
195
- keyFields: z.ZodOptional<z.ZodArray<z.ZodObject<{
196
- field: z.ZodString;
197
- values: z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
198
- }, z.core.$strict>>>;
199
- }, z.core.$strict>>;
200
- window: z.ZodOptional<z.ZodObject<{
201
- from: z.ZodOptional<z.ZodISODateTime>;
202
- until: z.ZodOptional<z.ZodISODateTime>;
203
- fromField: z.ZodOptional<z.ZodObject<{
204
- ref: z.ZodString;
205
- collection: z.ZodString;
206
- field: z.ZodString;
207
- }, z.core.$strict>>;
208
- untilField: z.ZodOptional<z.ZodObject<{
209
- ref: z.ZodString;
210
- collection: z.ZodString;
211
- field: z.ZodString;
212
- }, z.core.$strict>>;
213
- }, z.core.$strict>>;
214
- stampField: z.ZodOptional<z.ZodString>;
215
- selfUpdate: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>;
216
- selfTransitions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>;
217
- finalize: z.ZodOptional<z.ZodBoolean>;
218
- audience: z.ZodOptional<z.ZodLiteral<"participant">>;
219
- gateOn: z.ZodOptional<z.ZodObject<{
220
- phase: z.ZodString;
221
- match: z.ZodString;
222
- }, z.core.$strict>>;
223
- }, z.core.$strict>>>;
224
- }, z.core.$strict>>;
225
- views: z.ZodOptional<z.ZodArray<z.ZodObject<{
226
- id: z.ZodString;
227
- audience: z.ZodEnum<{
228
- public: "public";
229
- member: "member";
230
- participant: "participant";
231
- }>;
232
- path: z.ZodString;
233
- collections: z.ZodArray<z.ZodString>;
234
- }, z.core.$strict>>>;
235
- }, z.core.$strict>;
236
- export type AuthoredApp = z.infer<typeof AuthoredAppZ>;
237
- export type AuthoredCollectionConfig = z.infer<typeof CollectionConfigZ>;
238
- export type AuthoredSubmit = z.infer<typeof SubmitZ>;
239
- export type AuthoredMail = z.infer<typeof MailZ>;
240
- export type AuthoredAppResult = {
241
- ok: true;
242
- app: AuthoredApp;
243
- } | {
244
- ok: false;
245
- problems: string[];
246
- };
247
- /** Parse the authored declaration out of `app.json`'s text.
248
- *
249
- * Returns a LIST of problems rather than throwing, for the same reason
250
- * `loadAppManifest` returns a failure: the caller is a gate whose entire job
251
- * is to hand the author something to act on. Every problem is reported at
252
- * once — publish is a manual step, and a parser that stops at the first key
253
- * makes it N round trips. */
254
- export declare function parseAuthoredApp(raw: string): AuthoredAppResult;
255
- /** zod issues as one actionable line each: `public.submit.responses.auth: …`. */
256
- export declare function authoredProblems(error: z.ZodError): string[];
257
- export {};