@mulmoclaude/core 3.13.0 → 3.14.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.
@@ -11,6 +11,166 @@ import path from "node:path";
11
11
  import { randomBytes, randomUUID } from "node:crypto";
12
12
  import { cp, lstat, mkdir, open, readFile, readdir, rm, rmdir, stat, unlink, writeFile } from "node:fs/promises";
13
13
  import { z } from "zod";
14
+ //#region src/collection/server/appViews.ts
15
+ /** The audiences a view may be written for. A CLOSED set: each one names a
16
+ * tier with a rule behind it, so an unknown value has nowhere to be
17
+ * published to and is refused before it gets there. */
18
+ var VIEW_AUDIENCES = [
19
+ "public",
20
+ "member",
21
+ "participant"
22
+ ];
23
+ /** Where each audience's documents live under `apps/{aid}`. `public` is not
24
+ * here: it keeps `config/public` + `config/view`, which are already published
25
+ * and already read by a deployed runtime. */
26
+ var VIEW_TIER = {
27
+ member: "member",
28
+ participant: "roster"
29
+ };
30
+ /** The id `public.view` normalizes to. Fixed rather than derived, so two
31
+ * implementations of the same normalization cannot pick different ones. */
32
+ var PUBLIC_VIEW_ID = "public";
33
+ /** `config` is the projection's own document in every tier (`live:config`),
34
+ * so a view may not be called that — the two would be the same document. */
35
+ var RESERVED_VIEW_IDS = ["config"];
36
+ /** What an id may be.
37
+ *
38
+ * Narrow on purpose: this value is written by the author, and it becomes a
39
+ * Firestore document id under a `live:` / `staged:` prefix. Excluding `:`
40
+ * keeps the prefix and the id from running together; excluding `/`, `.` and
41
+ * `__…__` keeps it a legal document id that addresses the path it says. */
42
+ var VIEW_ID_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/;
43
+ var VIEW_ID_SHAPE = "must be lowercase letters, digits and hyphens, start with a letter or digit, and be at most 64 characters (e.g. front-desk)";
44
+ /** Both declarations of the same thing, in one file.
45
+ *
46
+ * Refused rather than merged or preferred: whichever way it were resolved,
47
+ * the author would have written two answers and been shown neither. */
48
+ var BOTH_FORMS = "app.json declares both `views` and `public.view`. These are the same thing — `public.view` is the older spelling — and publishing would have to choose one silently. Move the `public.view` entry into `views` as { id: \"public\", audience: \"public\", … } and delete it.";
49
+ /** The two declarations, as one list, or the refusal that they are both there.
50
+ *
51
+ * `public.view` becomes an entry under the reserved id, so everything
52
+ * downstream reads one shape and "which spelling was used" is decided once. */
53
+ function declaredViews(app) {
54
+ const legacy = app.public?.view;
55
+ const authored = app.views;
56
+ if (legacy !== void 0 && authored !== void 0) return {
57
+ ok: false,
58
+ problems: [BOTH_FORMS]
59
+ };
60
+ const views = (authored ?? []).map((view, index) => ({
61
+ id: view.id,
62
+ audience: view.audience,
63
+ path: view.path,
64
+ collections: view.collections,
65
+ where: `views[${index}]`
66
+ }));
67
+ if (legacy === void 0) return {
68
+ ok: true,
69
+ views
70
+ };
71
+ if (views.some((view) => view.id === "public")) return {
72
+ ok: false,
73
+ problems: [`views declares id '${PUBLIC_VIEW_ID}', which is reserved for the older \`public.view\` spelling. ${BOTH_FORMS}`]
74
+ };
75
+ return {
76
+ ok: true,
77
+ views: [...views, {
78
+ id: PUBLIC_VIEW_ID,
79
+ audience: "public",
80
+ path: legacy.path,
81
+ collections: legacy.collections,
82
+ where: "public.view"
83
+ }]
84
+ };
85
+ }
86
+ /** Whether one id may be used, and what to say when it may not. */
87
+ function viewIdProblems(view) {
88
+ if (RESERVED_VIEW_IDS.includes(view.id)) return [`${view.where}.id is '${view.id}', which is reserved: each audience's own declaration is published at that document id.`];
89
+ if (!VIEW_ID_PATTERN.test(view.id)) return [`${view.where}.id is '${view.id}': a view id ${VIEW_ID_SHAPE}. It becomes the document id this view is published at.`];
90
+ if (view.id === "public" && view.audience !== "public") return [`${view.where}.id is '${PUBLIC_VIEW_ID}' with audience '${view.audience}': that id belongs to the public page.`];
91
+ return [];
92
+ }
93
+ /** ONE public page per app, and the reason is the wire rather than taste.
94
+ *
95
+ * The public runtime reads a single `config/view` document and a single
96
+ * `config/public.view` declaration beside it. A second `audience: "public"`
97
+ * entry would pass every other check and then be published nowhere — and
98
+ * which of the two became the live page would depend on declaration order,
99
+ * silently. The member tiers have no such limit: `id` is their address, and
100
+ * each one gets its own document.
101
+ *
102
+ * The refusal is here rather than "one day we will support it" precisely
103
+ * because the failure is invisible: nothing errors, and the author sees a
104
+ * successful publish of a page nobody is served. */
105
+ function singlePublicProblems(views) {
106
+ const [first, ...rest] = views.filter((view) => view.audience === "public");
107
+ if (first === void 0) return [];
108
+ return rest.map((view) => `${view.where} is a second audience "public" view, after ${first.where}. The public page is published at ONE document (config/view), so only one of them could ever be served — and which, would depend on the order they were written in. Give the others audience "member" or "participant", which are addressed by id and may have as many as the app needs.`);
109
+ }
110
+ /** The one shape everything downstream reads.
111
+ *
112
+ * Every caller — the publish gate, the projection, the host that writes the
113
+ * documents — goes through this, so "which declaration was used" is decided
114
+ * exactly once. */
115
+ function normalizeViews(app) {
116
+ const declared = declaredViews(app);
117
+ if (!declared.ok) return declared;
118
+ const problems = [...singlePublicProblems(declared.views)];
119
+ const seen = /* @__PURE__ */ new Map();
120
+ for (const view of declared.views) {
121
+ problems.push(...viewIdProblems(view));
122
+ const first = seen.get(view.id);
123
+ if (first === void 0) {
124
+ seen.set(view.id, view.where);
125
+ continue;
126
+ }
127
+ problems.push(`${view.where}.id is '${view.id}', which ${first} already uses. The id is the document a view is published at, so two of them are one page — whichever was written second would silently replace the first, in staging and again at publish.`);
128
+ }
129
+ return problems.length > 0 ? {
130
+ ok: false,
131
+ problems
132
+ } : {
133
+ ok: true,
134
+ views: declared.views
135
+ };
136
+ }
137
+ /** How a participant reaches `cid`, or null if they cannot.
138
+ *
139
+ * Mirrors the rules' read branches for someone holding no role:
140
+ * `partRead` (the whole collection) and `ownRow` (their own record, found
141
+ * by the submit declaration's `emailField` or by a uid-derived id).
142
+ *
143
+ * `participantRead` is a PARAMETER rather than read off the manifest, and
144
+ * that is the whole point of the signature. Publish does not promote the
145
+ * manifest's value: `projectPublish` overwrites `participantRead` with what
146
+ * the STAGED schemas carry, so a cid added since the last deploy is in the
147
+ * manifest and not in the rules. Deriving the scope from the manifest would
148
+ * publish `scope: "all"` for a collection the promoted rules then deny —
149
+ * and removing one gives the mirror-image false refusal. The caller passes
150
+ * the set that will actually be in force. */
151
+ function participantScope(app, cid, participantRead) {
152
+ if (participantRead.includes(cid)) return {
153
+ cid,
154
+ scope: "all"
155
+ };
156
+ const submit = app.public?.submit?.[cid];
157
+ if (submit?.emailField !== void 0) return {
158
+ cid,
159
+ scope: "own",
160
+ emailField: submit.emailField
161
+ };
162
+ if (submit?.idFrom === "auth.uid") return {
163
+ cid,
164
+ scope: "own",
165
+ ownDocId: "auth.uid"
166
+ };
167
+ return null;
168
+ }
169
+ /** The document ids one tier uses. `live:` and `staged:` are the only two
170
+ * prefixes, so a single `match` covers the projection and every view. */
171
+ var viewDocId = (stage, viewId) => `${stage}:${viewId}`;
172
+ var VIEW_CONFIG_ID = "config";
173
+ //#endregion
14
174
  //#region src/collection/server/publishManifest.ts
15
175
  /** A collection id / app id, held to the one name rule (`SAFE_SLUG_PATTERN`)
16
176
  * that `sharedCollectionKey` applies. Stated once so a path built later
@@ -264,6 +424,28 @@ var PublicZ = z.object({
264
424
  }).strict().optional(),
265
425
  submit: z.record(NameZ, SubmitZ).optional()
266
426
  }).strict();
427
+ /** One page the app shows, and who it is for.
428
+ *
429
+ * Generalised from `public.view` (which is still accepted, and normalizes
430
+ * into this — see `appViews.ts`). The audience is what decides which document
431
+ * the HTML is published to, and therefore who may read it: a rule cannot hide
432
+ * a field, so "the front desk sees this" is a place, not a filter.
433
+ *
434
+ * `id` is not decoration. It becomes the document id the page is published
435
+ * at, which is what lets one audience have more than one page — the front
436
+ * desk and the stock room — and what lets a withdrawn view be found and
437
+ * deleted. Its grammar is enforced at the gate, not here, so the refusal can
438
+ * say what the value is used for.
439
+ *
440
+ * `collections` is declared rather than inferred, for the reason `public.view`
441
+ * gives: an inferred list renders a perfect page with no data in it, and
442
+ * nothing anywhere says why. */
443
+ var ViewZ = z.object({
444
+ id: z.string().trim().min(1),
445
+ audience: z.enum(VIEW_AUDIENCES),
446
+ path: z.string().trim().min(1),
447
+ collections: z.array(NameZ).min(1)
448
+ }).strict();
267
449
  /** The URL name an app is handed out under: `https://<host>/{slug}`.
268
450
  *
269
451
  * A SEPARATE name from the `aid`, and that separation is the point (design
@@ -312,7 +494,10 @@ var AuthoredAppZ = z.object({
312
494
  members: MembersZ,
313
495
  collections: z.record(NameZ, CollectionConfigZ).optional(),
314
496
  participantRead: z.array(NameZ).optional(),
315
- public: PublicZ.optional()
497
+ public: PublicZ.optional(),
498
+ /** The app's pages, per audience. See {@link ViewZ}; `public.view` is the
499
+ * older spelling of the `public` one and normalizes into this list. */
500
+ views: z.array(ViewZ).optional()
316
501
  }).strict();
317
502
  /** Parse the authored declaration out of `app.json`'s text.
318
503
  *
@@ -441,11 +626,14 @@ function projectApp(authored, schemas, stamp, existing) {
441
626
  publishedDirty: stamp.dirty === true ? true : void 0,
442
627
  previousPublished: previousOf(existing)
443
628
  });
629
+ const normalized = normalizeViews(authored);
630
+ if (!normalized.ok) throw new Error(`publish: views declaration is not publishable (${normalized.problems.join(" ")})`);
631
+ const publicView = normalized.views.find((view) => view.audience === "public");
444
632
  const config = {
445
633
  enabled: authored.public?.enabled === true,
446
634
  read: authored.public?.read ?? [],
447
635
  submit,
448
- ...authored.public?.view === void 0 ? {} : { view: { collections: authored.public.view.collections } },
636
+ ...publicView === void 0 ? {} : { view: { collections: publicView.collections } },
449
637
  publishedAt: stamp.publishedAt
450
638
  };
451
639
  if (authored.name !== void 0) config.name = authored.name;
@@ -596,6 +784,64 @@ var appSlugDoc = (aid, published) => ({
596
784
  var appStagingPath = (aid) => `apps/${aid}/staging`;
597
785
  /** The public-config documents' parent path. */
598
786
  var appConfigPath = (aid) => `apps/${aid}/config`;
787
+ /** Where one audience's pages live. `member` is read by anyone holding a role;
788
+ * `roster` by anyone on the roster, participants included. */
789
+ var appViewTierPath = (aid, tier) => `apps/${aid}/${tier}`;
790
+ /** What one audience may see of one collection.
791
+ *
792
+ * For `member` this is always the whole collection: every read branch a role
793
+ * opens (`readerOf`) is unscoped. Whether THIS member holds the role is not
794
+ * knowable here — one projection is read by every member of the tier — and is
795
+ * settled where it can be, by the entrance trying the read.
796
+ *
797
+ * For `participant` it is the rules' own answer, which is why it can be null:
798
+ * a participant with neither `participantRead` nor an own-row submit path
799
+ * cannot read the collection at all, and a page handed it would fail rather
800
+ * than render less. */
801
+ function scopeFor(authored, audience, cid, participantRead) {
802
+ return audience === "member" ? {
803
+ cid,
804
+ scope: "all"
805
+ } : participantScope(authored, cid, participantRead);
806
+ }
807
+ /** Project the declaration into the per-audience documents.
808
+ *
809
+ * Pure, like `projectApp`: the HTML is not here (the host reads the files),
810
+ * and neither is the clock. What is here is the answer to "what may this
811
+ * audience read, and how" — computed once, so the page never has to guess and
812
+ * never has to discover it from a denial. */
813
+ function projectAppViews(authored, stamp, promoted) {
814
+ const participantRead = promoted?.participantRead ?? authored.participantRead ?? [];
815
+ const normalized = normalizeViews(authored);
816
+ if (!normalized.ok) throw new Error(`publish: views declaration is not publishable (${normalized.problems.join(" ")})`);
817
+ return ["member", "participant"].map((audience) => {
818
+ const views = normalized.views.filter((view) => view.audience === audience);
819
+ const cids = [...new Set(views.flatMap((view) => view.collections))];
820
+ const declaredSubmit = authored.public?.submit ?? {};
821
+ const submit = Object.fromEntries(cids.flatMap((cid) => {
822
+ const spec = declaredSubmit[cid];
823
+ return spec === void 0 ? [] : [[cid, projectSubmit(spec)]];
824
+ }));
825
+ const config = {
826
+ views: views.map((view) => ({
827
+ id: view.id,
828
+ collections: view.collections.map((cid) => scopeFor(authored, audience, cid, participantRead)).filter((scope) => scope !== null)
829
+ })),
830
+ submit,
831
+ publishedAt: stamp.publishedAt
832
+ };
833
+ if (authored.name !== void 0) config.name = authored.name;
834
+ return {
835
+ tier: VIEW_TIER[audience],
836
+ audience,
837
+ config,
838
+ views
839
+ };
840
+ });
841
+ }
842
+ /** The document a tier's projection is published at. Beside the views
843
+ * themselves, under one `match` — see `firestore.rules`. */
844
+ var viewConfigDocId = (stage) => viewDocId(stage, VIEW_CONFIG_ID);
599
845
  //#endregion
600
846
  //#region src/collection/server/publishChecks.ts
601
847
  /** Does this submit declaration bind a record to the submitter's identity?
@@ -871,7 +1117,7 @@ function publishProblems(app, collections, publisherEmail) {
871
1117
  ...windowRefProblems(app, collections),
872
1118
  ...idTargetProblems(app, collections),
873
1119
  ...mirrorProblems(app, collections),
874
- ...publicViewProblems(app, collections)
1120
+ ...viewProblems(app, collections)
875
1121
  ];
876
1122
  }
877
1123
  /** A public submission must NOT be allowed to name its own primary key.
@@ -1002,24 +1248,56 @@ function mirrorOfProblems(app, cid, collection, known, names) {
1002
1248
  if (app.public?.submit?.[authority]?.mirror !== cid) return [`collections.${cid}.mirrorOf names '${authority}', but public.submit.${authority} does not declare mirror: "${cid}". Only the pair keeps the projection honest: without the other half a record can be created without moving this one, and the public page goes on offering something that is already taken.`];
1003
1249
  return [];
1004
1250
  }
1005
- /** What the public view is handed. Declared, never inferred — a view whose
1006
- * datasets were guessed from `public.read` renders perfectly and draws an
1007
- * empty grid, with nothing in the page, the rules or the log to say why. */
1008
- function publicViewProblems(app, collections) {
1009
- const view = app.public?.view;
1010
- if (view === void 0) return [];
1251
+ /** What each view is handed, and whether its audience can actually read it.
1252
+ *
1253
+ * Declared, never inferred — a view whose datasets were guessed from
1254
+ * `public.read` renders perfectly and draws an empty grid, with nothing in
1255
+ * the page, the rules or the log to say why.
1256
+ *
1257
+ * The reachability check below is that same failure, once per audience. A
1258
+ * `public` view naming a collection outside `public.read` draws nothing; a
1259
+ * `participant` view naming one the participant reaches by neither
1260
+ * `participantRead` nor their own row is worse, because an unscoped list on
1261
+ * an own-row collection is DENIED rather than narrowed — the page does not
1262
+ * render less, it fails.
1263
+ *
1264
+ * There is deliberately no such check for `member`. Every read a role opens
1265
+ * is unscoped, and WHICH role a given member holds is not a property of the
1266
+ * declaration: a stylist scoped to `bookings` and an owner read the same
1267
+ * projection. That one is settled at the entrance, by trying the read. */
1268
+ /** The path, for one view. The SAME validator the host's own custom views use,
1269
+ * rather than a second opinion about what a safe view path is.
1270
+ *
1271
+ * Two ad-hoc attempts were wrong here in the same afternoon: a prefix-and-
1272
+ * suffix test let `views/../../secrets.html` through, and `views/[^/]+\.html`
1273
+ * still let `views/..\..\secrets.html` through, because a backslash is not a
1274
+ * slash on this side of the check and IS a separator on Windows. This one
1275
+ * rejects `..`, backslashes, leading slashes and anything outside
1276
+ * `[A-Za-z0-9._-]` per segment.
1277
+ *
1278
+ * It matters more here than for a host view: the host reads this path to
1279
+ * decide which file to copy onto a document other people read — for a public
1280
+ * view, a document whose rule is `allow read: if true`, so the blast radius of
1281
+ * a bad path is the world rather than the author's own iframe. Nested paths
1282
+ * ARE allowed by the shared validator; the extra `views/<one name>.html` shape
1283
+ * is this publisher's own narrowing, kept because there is no reason for a
1284
+ * published view to live in a subdirectory. */
1285
+ function viewPathProblems(view) {
1286
+ if (isSafeCustomViewPath(view.path) && view.path.split("/").length === 2) return [];
1287
+ return [`${view.where}.path is '${view.path}': a published view is exactly one HTML file directly inside the collection's own views/ directory (e.g. views/booking.html) — no sub-directories, and no segments that climb out of it. The host reads this as a file to publish.`];
1288
+ }
1289
+ /** One dataset, for one view: does it exist here, and can the audience it is
1290
+ * handed to actually read it? */
1291
+ function viewCollectionProblems(app, view, cid, known) {
1292
+ if (!known.has(cid)) return [`${view.where}.collections names '${cid}', which is not a shared collection in this repository. Shared collections here: ${known.size > 0 ? [...known].sort().join(", ") : "(none)"}.`];
1293
+ if (view.audience === "public" && !(app.public?.read ?? []).includes(cid)) return [`${view.where}.collections names '${cid}', which is not in public.read: the page reads these with the VISITOR's permissions, so the rules refuse the read and the view draws an empty page. Nothing errors — this is the failure that looks like a working view with no data.`];
1294
+ return [];
1295
+ }
1296
+ function viewProblems(app, collections) {
1297
+ const normalized = normalizeViews(app);
1298
+ if (!normalized.ok) return normalized.problems;
1011
1299
  const known = new Set(collections.map((collection) => collection.cid));
1012
- const readable = new Set(app.public?.read ?? []);
1013
- const problems = [];
1014
- if (!isSafeCustomViewPath(view.path) || view.path.split("/").length !== 2) problems.push(`public.view.path is '${view.path}': a published view is exactly one HTML file directly inside the collection's own views/ directory (e.g. views/booking.html) — no sub-directories, and no segments that climb out of it. The host reads this as a file to publish, and what it publishes is world-readable.`);
1015
- for (const cid of view.collections) {
1016
- if (!known.has(cid)) {
1017
- problems.push(`public.view.collections names '${cid}', which is not a shared collection in this repository. Shared collections here: ${known.size > 0 ? [...known].sort().join(", ") : "(none)"}.`);
1018
- continue;
1019
- }
1020
- if (!readable.has(cid)) problems.push(`public.view.collections names '${cid}', which is not in public.read: the page reads these with the VISITOR's permissions, so the rules refuse the read and the view draws an empty page. Nothing errors — this is the failure that looks like a working view with no data.`);
1021
- }
1022
- return problems;
1300
+ return normalized.views.flatMap((view) => [...viewPathProblems(view), ...view.collections.flatMap((cid) => viewCollectionProblems(app, view, cid, known))]);
1023
1301
  }
1024
1302
  /** What publish will actually promote, checked as the PAIR it becomes.
1025
1303
  *
@@ -1051,9 +1329,25 @@ function promotedRoleProblems(app, staged) {
1051
1329
  return [
1052
1330
  ...promotedAssigneeProblems(app, promoted, stagedCids),
1053
1331
  ...promotedMirrorProblems(app, promoted, stagedCids),
1054
- ...promotedRefFieldProblems(app, staged)
1332
+ ...promotedRefFieldProblems(app, staged),
1333
+ ...promotedParticipantViewProblems(app, staged)
1055
1334
  ];
1056
1335
  }
1336
+ /** A participant's page, checked against the `participantRead` publish will
1337
+ * actually PROMOTE.
1338
+ *
1339
+ * Not against the manifest's. `projectPublish` overwrites `participantRead`
1340
+ * with what the staged schemas carry, so a cid added to the manifest since the
1341
+ * last deploy is not in the rules — and a page written for it would be
1342
+ * published, offered, and then refused the read. The manifest half of this
1343
+ * file cannot see that; the promoted half can, which is why the check lives
1344
+ * here rather than beside the other view checks. */
1345
+ function promotedParticipantViewProblems(app, staged) {
1346
+ const normalized = normalizeViews(app);
1347
+ if (!normalized.ok) return [];
1348
+ const participantRead = stagedRuleConfig(staged).participantRead ?? [];
1349
+ return normalized.views.filter((view) => view.audience === "participant").flatMap((view) => view.collections.filter((cid) => participantScope(app, cid, participantRead) === null).map((cid) => `${view.where}.collections names '${cid}', which a participant cannot read once this publishes: it is not in the participantRead that DEPLOY staged, and public.submit.${cid} declares neither an emailField nor idFrom "auth.uid", so there is no row the rules would call theirs. The page would be refused the read, not handed fewer records. (Adding it to participantRead in app.json is not enough — deploy first.)`));
1350
+ }
1057
1351
  /** The FIELDS a rule reads off another record — `idIn.where.field` and the two
1058
1352
  * window bounds — checked against the schema publish is about to promote.
1059
1353
  *
@@ -3258,6 +3552,6 @@ function makeManageCollectionTool(deps = {}) {
3258
3552
  };
3259
3553
  }
3260
3554
  //#endregion
3261
- export { AuthoredAppZ as $, runQueryOverRows as A, APPS_COLLECTION as B, STORE_UNREADABLE as C, recordFieldProblem as D, compileRecordZ as E, readCustomViewI18n as F, appSlugDoc as G, PUBLIC_CONFIG_DOC as H, readSkillTemplate as I, projectDeploy as J, appStagingPath as K, bindsSubmitterIdentity as L, buildCollectionActionSeedPrompt as M, promptPathsFor as N, runCollectionQuery as O, readCustomViewHtml as P, APP_ROLES as Q, promotedRoleProblems as R, MAX_RECORD_ISSUES as S, validateRecordObject as T, appConfigPath as U, APP_SLUGS_COLLECTION as V, appSchemasPath as W, promoteSchema as X, projectPublish as Y, stagedRuleConfig as Z, computeCollectionIcon as _, deleteCollection as a, applyMutateAction as b, computeSuccessor as c, isTriggerDue as d, parseAuthoredApp as et, maybeSpawnSuccessor as f, ONE_SECOND_MS as g, successorId as h, deleteCustomView as i, buildActionSeedPrompt as j, enrichItems as k, daysInMonth as l, resolveEvery as m, MAX_UNSELECTIVE_ITEMS as n, deleteCollectionRefusalMessage as o, parseCivil as p, projectApp as q, makeManageCollectionTool as r, advanceTriggerDate as s, MAX_SCHEMA_ISSUES as t, formatCivil as u, buildWorkspaceOntology as v, validateCollectionRecords as w, firstMutateParamProblem as x, schemaRelations as y, publishProblems as z };
3555
+ export { stagedRuleConfig as $, runQueryOverRows as A, APPS_COLLECTION as B, STORE_UNREADABLE as C, recordFieldProblem as D, compileRecordZ as E, readCustomViewI18n as F, appSlugDoc as G, PUBLIC_CONFIG_DOC as H, readSkillTemplate as I, projectApp as J, appStagingPath as K, bindsSubmitterIdentity as L, buildCollectionActionSeedPrompt as M, promptPathsFor as N, runCollectionQuery as O, readCustomViewHtml as P, promoteSchema as Q, promotedRoleProblems as R, MAX_RECORD_ISSUES as S, validateRecordObject as T, appConfigPath as U, APP_SLUGS_COLLECTION as V, appSchemasPath as W, projectDeploy as X, projectAppViews as Y, projectPublish as Z, computeCollectionIcon as _, deleteCollection as a, RESERVED_VIEW_IDS as at, applyMutateAction as b, computeSuccessor as c, VIEW_ID_PATTERN as ct, isTriggerDue as d, participantScope as dt, viewConfigDocId as et, maybeSpawnSuccessor as f, viewDocId as ft, ONE_SECOND_MS as g, successorId as h, deleteCustomView as i, PUBLIC_VIEW_ID as it, buildActionSeedPrompt as j, enrichItems as k, daysInMonth as l, VIEW_TIER as lt, resolveEvery as m, MAX_UNSELECTIVE_ITEMS as n, AuthoredAppZ as nt, deleteCollectionRefusalMessage as o, VIEW_AUDIENCES as ot, parseCivil as p, appViewTierPath as q, makeManageCollectionTool as r, parseAuthoredApp as rt, advanceTriggerDate as s, VIEW_CONFIG_ID as st, MAX_SCHEMA_ISSUES as t, APP_ROLES as tt, formatCivil as u, normalizeViews as ut, buildWorkspaceOntology as v, validateCollectionRecords as w, firstMutateParamProblem as x, schemaRelations as y, publishProblems as z };
3262
3556
 
3263
- //# sourceMappingURL=server-DGjmK9vU.js.map
3557
+ //# sourceMappingURL=server-CQSF5fHe.js.map