@mulmoclaude/core 3.6.0 → 3.10.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 (71) hide show
  1. package/dist/collection/core/itemId.d.ts +28 -0
  2. package/dist/collection/firestore.cjs +9 -1
  3. package/dist/collection/firestore.cjs.map +1 -1
  4. package/dist/collection/firestore.js +10 -2
  5. package/dist/collection/firestore.js.map +1 -1
  6. package/dist/collection/index.cjs +55 -66
  7. package/dist/collection/index.cjs.map +1 -1
  8. package/dist/collection/index.d.ts +1 -1
  9. package/dist/collection/index.js +3 -15
  10. package/dist/collection/index.js.map +1 -1
  11. package/dist/collection/registry/server/index.cjs +2 -2
  12. package/dist/collection/registry/server/index.js +2 -2
  13. package/dist/collection/server/firestoreDocs.d.ts +18 -0
  14. package/dist/collection/server/host.d.ts +40 -0
  15. package/dist/collection/server/index.cjs +25 -4
  16. package/dist/collection/server/index.d.ts +4 -1
  17. package/dist/collection/server/index.js +4 -4
  18. package/dist/collection/server/io.d.ts +5 -3
  19. package/dist/collection/server/publishChecks.d.ts +29 -0
  20. package/dist/collection/server/publishManifest.d.ts +181 -0
  21. package/dist/collection/server/publishProject.d.ts +255 -0
  22. package/dist/collection/server/validate.d.ts +12 -0
  23. package/dist/collection-watchers/index.cjs +70 -26
  24. package/dist/collection-watchers/index.cjs.map +1 -1
  25. package/dist/collection-watchers/index.js +67 -23
  26. package/dist/collection-watchers/index.js.map +1 -1
  27. package/dist/{discovery-CdkaURVY.js → discovery-B9zdNkgW.js} +189 -18
  28. package/dist/discovery-B9zdNkgW.js.map +1 -0
  29. package/dist/{discovery-Cw5xWzZD.cjs → discovery-BsXiDZBR.cjs} +226 -43
  30. package/dist/discovery-BsXiDZBR.cjs.map +1 -0
  31. package/dist/feeds/index.cjs +4 -4
  32. package/dist/feeds/index.js +2 -2
  33. package/dist/feeds/server/index.cjs +6 -6
  34. package/dist/feeds/server/index.js +4 -4
  35. package/dist/firestore/listen.d.ts +4 -0
  36. package/dist/google/index.cjs +5 -5
  37. package/dist/google/index.cjs.map +1 -1
  38. package/dist/google/index.js +2 -2
  39. package/dist/{ingestTypes-CEi-Ot7F.js → ingestTypes-B312BwXg.js} +2 -2
  40. package/dist/{ingestTypes-CEi-Ot7F.js.map → ingestTypes-B312BwXg.js.map} +1 -1
  41. package/dist/{ingestTypes-iaiq33Ph.cjs → ingestTypes-B4XfrUoq.cjs} +3 -3
  42. package/dist/{ingestTypes-iaiq33Ph.cjs.map → ingestTypes-B4XfrUoq.cjs.map} +1 -1
  43. package/dist/{calendarGrid-CQ8MVSRb.js → itemId-DfTm08jm.js} +34 -2
  44. package/dist/itemId-DfTm08jm.js.map +1 -0
  45. package/dist/{calendarGrid-DGILaVxI.cjs → itemId-Dzrr3vs5.cjs} +45 -1
  46. package/dist/itemId-Dzrr3vs5.cjs.map +1 -0
  47. package/dist/listen-D5av7GzB.js +22 -0
  48. package/dist/listen-D5av7GzB.js.map +1 -0
  49. package/dist/listen-UZA68Upt.cjs +45 -0
  50. package/dist/listen-UZA68Upt.cjs.map +1 -0
  51. package/dist/{promptSafety-CpiME8pj.js → promptSafety-CSdyp7RF.js} +2 -2
  52. package/dist/{promptSafety-CpiME8pj.js.map → promptSafety-CSdyp7RF.js.map} +1 -1
  53. package/dist/{promptSafety-NNGViiCr.cjs → promptSafety-CWqYq-NS.cjs} +8 -8
  54. package/dist/{promptSafety-NNGViiCr.cjs.map → promptSafety-CWqYq-NS.cjs.map} +1 -1
  55. package/dist/remote-host/server/hostRunner.d.ts +2 -4
  56. package/dist/remote-host/server/index.cjs +4 -19
  57. package/dist/remote-host/server/index.cjs.map +1 -1
  58. package/dist/remote-host/server/index.js +1 -16
  59. package/dist/remote-host/server/index.js.map +1 -1
  60. package/dist/{server-DdHaX_Uu.js → server-BNe8XMtu.js} +677 -6
  61. package/dist/server-BNe8XMtu.js.map +1 -0
  62. package/dist/{server-9mFaGLdq.cjs → server-CgkkWWBd.cjs} +807 -22
  63. package/dist/server-CgkkWWBd.cjs.map +1 -0
  64. package/package.json +1 -1
  65. package/dist/calendarGrid-CQ8MVSRb.js.map +0 -1
  66. package/dist/calendarGrid-DGILaVxI.cjs.map +0 -1
  67. package/dist/collection/core/shortHexId.d.ts +0 -8
  68. package/dist/discovery-CdkaURVY.js.map +0 -1
  69. package/dist/discovery-Cw5xWzZD.cjs.map +0 -1
  70. package/dist/server-9mFaGLdq.cjs.map +0 -1
  71. package/dist/server-DdHaX_Uu.js.map +0 -1
@@ -1,15 +1,674 @@
1
1
  import { a as isErrorWithCode, c as isStringArray, l as isUnknownArray, s as isRecord, t as errorMessage } from "./dist-D8zokgGo.js";
2
2
  import { n as writeFileAtomic } from "./root-BMroU_mB.js";
3
3
  import { n as toPosixRelPath } from "./relPath-DW8MC8VO.js";
4
- import { F as isFieldDrivenEvery, L as storageKindFor, P as embedTargetId, R as fieldText, j as COMPUTED_TYPES, l as parseIsoDate, u as parseIsoDateTime, z as fieldTextOrNull } from "./calendarGrid-CQ8MVSRb.js";
5
- import { C as actionVisible, _ as uniqueRefTargets, b as projectBacklinkRow, c as selectDynamicRecord, f as itemIsDone, g as uniqueEmbedTargets, h as uniqueBacklinkSources, i as ownProp, n as deriveAll, o as firstDateField, s as resolveIcon, t as defangForPrompt, v as backlinkRows, x as rollupValue, y as coerceNumeric } from "./promptSafety-CpiME8pj.js";
6
- import { B as SCHEMA_FILE$1, C as CollectionQueryZ, J as isBackendUnavailable, K as safeSlugName, O as isRegularFile, S as compileJsonlQuery, U as resolveDataDir, V as isContainedInRoot, W as resolveTemplatePath, X as archiveDir, at as log, b as queryCsv, c as resolveMutateSet, d as storeFor, f as checkpointSqliteDatabase, i as resolvePrimaryField, j as resolveCreateItemId, m as cacheDir, n as discoverCollections, nt as getWorkspaceRoot, pt as stagingSkillDir, r as loadCollection, rt as isPresetSlug$1, s as CollectionSchemaZ, u as readOnlyRefusal, y as normalizeCsvValue } from "./discovery-CdkaURVY.js";
4
+ import { B as fieldText, I as embedTargetId, L as isFieldDrivenEvery, N as COMPUTED_TYPES, V as fieldTextOrNull, d as parseIsoDate, f as parseIsoDateTime, x as isValidCollectionName, z as storageKindFor } from "./itemId-DfTm08jm.js";
5
+ import { C as actionVisible, _ as uniqueRefTargets, b as projectBacklinkRow, c as selectDynamicRecord, f as itemIsDone, g as uniqueEmbedTargets, h as uniqueBacklinkSources, i as ownProp, n as deriveAll, o as firstDateField, s as resolveIcon, t as defangForPrompt, v as backlinkRows, x as rollupValue, y as coerceNumeric } from "./promptSafety-CSdyp7RF.js";
6
+ import { B as SCHEMA_FILE$1, C as CollectionQueryZ, I as parseAppManifest, J as isBackendUnavailable, K as safeSlugName, O as isRegularFile, S as compileJsonlQuery, U as resolveDataDir, V as isContainedInRoot, W as resolveTemplatePath, X as archiveDir, b as queryCsv, c as resolveMutateSet, d as storeFor, f as checkpointSqliteDatabase, ht as stagingSkillDir, i as resolvePrimaryField, it as isPresetSlug$1, j as resolveCreateItemId, m as cacheDir, n as discoverCollections, nt as getWorkspaceRoot, ot as log, r as loadCollection, s as CollectionSchemaZ, u as readOnlyRefusal, y as normalizeCsvValue } from "./discovery-B9zdNkgW.js";
7
7
  import { ingestStatePath } from "./feeds/paths.js";
8
8
  import { mirrorSkillWrite } from "./skill-bridge/index.js";
9
9
  import path from "node:path";
10
10
  import { randomBytes, randomUUID } from "node:crypto";
11
11
  import { cp, lstat, mkdir, open, readFile, readdir, rm, rmdir, stat, unlink, writeFile } from "node:fs/promises";
12
12
  import { z } from "zod";
13
+ //#region src/collection/server/publishManifest.ts
14
+ /** A collection id / app id, held to the one name rule (`SAFE_SLUG_PATTERN`)
15
+ * that `sharedCollectionKey` applies. Stated once so a path built later
16
+ * cannot be a way around it. */
17
+ var NameZ = z.string().refine(isValidCollectionName, { message: "is not a valid id (letters, digits, '-' and '_' only)" });
18
+ /** An address on the roster. Not validated as an email beyond "has an @":
19
+ * the rules compare it to `request.auth.token.email` verbatim, so any
20
+ * narrowing here would refuse addresses Firebase itself accepts. */
21
+ var EmailZ = z.string().trim().min(3).includes("@");
22
+ /** The four roles the deployed rules understand. `participant` is the layer
23
+ * that is NAMED but reads only its own rows — see `readerOf` vs `listedIn`. */
24
+ var APP_ROLES = [
25
+ "owner",
26
+ "editor",
27
+ "viewer",
28
+ "participant"
29
+ ];
30
+ var RoleZ = z.enum(APP_ROLES);
31
+ /** `{ email: { "*" | cid: role } }`. The `"*"` key is the app-wide role; a
32
+ * member may hold per-collection roles only (the stylist who is editor of
33
+ * bookings and viewer of everything else). */
34
+ var MembersZ = z.record(EmailZ, z.record(z.union([z.literal("*"), NameZ]), RoleZ));
35
+ /** The declarative mail queue, as the rules re-derive it: a transition of the
36
+ * status field, a recipient read off the RECORD, and a fixed template. */
37
+ var MailZ = z.object({
38
+ toField: z.string().trim().min(1),
39
+ on: z.record(z.string().trim().min(1), z.object({
40
+ from: z.array(z.string().trim().min(1)).min(1),
41
+ to: z.string().trim().min(1)
42
+ }).strict()),
43
+ dataFields: z.array(z.string().trim().min(1)).optional()
44
+ }).strict();
45
+ /** What the rules read out of `collections[cid]`. NOT the schema — the schema
46
+ * is published beside it, untouched, for clients to render from. */
47
+ var CollectionConfigZ = z.object({
48
+ statusField: z.string().trim().min(1).optional(),
49
+ /** `{ initial: [...], <status>: [<status>...] }`. Binds writers too, and
50
+ * binds `create` — that is the point of publishing it. */
51
+ transitions: z.record(z.string().trim().min(1), z.array(z.string().trim().min(1))).optional(),
52
+ immutable: z.boolean().optional(),
53
+ submitOnly: z.boolean().optional(),
54
+ peerVisibility: z.enum(["public", "hidden"]).optional(),
55
+ revealGated: z.boolean().optional(),
56
+ gatedFrom: NameZ.optional(),
57
+ revealBy: z.string().trim().min(1).optional(),
58
+ mail: MailZ.optional(),
59
+ /** Which fields an aggregate groups by. Declared here rather than in the
60
+ * schema for the same reason as everything else in this file — the schema
61
+ * has no `aggregate` key yet — and it is here at all because the
62
+ * invariant that guards it ("every aggregation key is a CHECKED field")
63
+ * is about `public.submit`, which is an app-level declaration. Published
64
+ * as-is; the rules never read it. */
65
+ aggregate: z.object({ by: z.array(z.string().trim().min(1)).min(1) }).strict().optional()
66
+ }).strict();
67
+ /** An authored submit window. ISO strings, because `app.json` is JSON and a
68
+ * Firestore `Timestamp` has no JSON form. Publish lowers it to epoch millis —
69
+ * the rules do not coerce strings, so an ISO string reaching Firestore is a
70
+ * type error that fails CLOSED (`inWindow` refuses every submission and the
71
+ * author sees "nobody can submit", not an error). */
72
+ var WindowZ = z.object({
73
+ from: z.iso.datetime().optional(),
74
+ until: z.iso.datetime().optional()
75
+ }).strict();
76
+ var ValidateZ = z.object({
77
+ required: z.array(z.string().trim().min(1)).optional(),
78
+ /** Capped at two by the rules themselves: rules have no iteration, so
79
+ * `keyFieldsOk` is unrolled. A third would be accepted here and silently
80
+ * unchecked there. */
81
+ keyFields: z.array(z.object({
82
+ field: z.string().trim().min(1),
83
+ values: z.array(z.union([
84
+ z.string(),
85
+ z.number(),
86
+ z.boolean()
87
+ ])).min(1)
88
+ }).strict()).optional()
89
+ }).strict();
90
+ var SubmitZ = z.object({
91
+ auth: z.enum([
92
+ "none",
93
+ "anonymous",
94
+ "verifiedEmail"
95
+ ]),
96
+ emailField: z.string().trim().min(1).optional(),
97
+ createFields: z.array(z.string().trim().min(1)).min(1),
98
+ initialStatus: z.string().trim().min(1).optional(),
99
+ idFrom: z.enum([
100
+ "auto",
101
+ "auth.uid",
102
+ "auth.uid+field"
103
+ ]).optional(),
104
+ idField: z.string().trim().min(1).optional(),
105
+ validate: ValidateZ.optional(),
106
+ window: WindowZ.optional(),
107
+ /** Per CURRENT STATUS, never a flat list: a flat list lets a customer move
108
+ * an approved booking's `startAt` without anyone re-approving it. */
109
+ selfUpdate: z.record(z.string().trim().min(1), z.array(z.string().trim().min(1))).optional(),
110
+ selfTransitions: z.record(z.string().trim().min(1), z.array(z.string().trim().min(1))).optional(),
111
+ finalize: z.boolean().optional(),
112
+ audience: z.literal("participant").optional(),
113
+ gateOn: z.object({
114
+ phase: z.string().trim().min(1),
115
+ match: z.string().trim().min(1)
116
+ }).strict().optional()
117
+ }).strict();
118
+ var PublicZ = z.object({
119
+ /** The master switch. Anonymous submission (`auth: "none"`) needs it as
120
+ * well as its own declaration. */
121
+ enabled: z.boolean().optional(),
122
+ read: z.array(NameZ).optional(),
123
+ submit: z.record(NameZ, SubmitZ).optional()
124
+ }).strict();
125
+ /** The whole authored declaration.
126
+ *
127
+ * `owner` is accepted but is NOT the published value — publish stamps the
128
+ * publisher's uid (or carries the existing one forward, which is what the
129
+ * rules require on update) and refuses a declaration that disagrees. It is
130
+ * accepted rather than banned because the sample app.json in the design note
131
+ * shows it, and a hard refusal on a key the samples contain would be a worse
132
+ * first experience than a message naming the mismatch. */
133
+ var AuthoredAppZ = z.object({
134
+ aid: NameZ,
135
+ name: z.string().trim().min(1).optional(),
136
+ /** Per-worktree app id (design D6, implementation order 7). Accepted so a
137
+ * repository already carrying it parses; nothing reads it yet. */
138
+ aidEnv: z.string().trim().min(1).optional(),
139
+ owner: z.string().trim().min(1).optional(),
140
+ members: MembersZ,
141
+ collections: z.record(NameZ, CollectionConfigZ).optional(),
142
+ participantRead: z.array(NameZ).optional(),
143
+ public: PublicZ.optional()
144
+ }).strict();
145
+ /** Parse the authored declaration out of `app.json`'s text.
146
+ *
147
+ * Returns a LIST of problems rather than throwing, for the same reason
148
+ * `loadAppManifest` returns a failure: the caller is a gate whose entire job
149
+ * is to hand the author something to act on. Every problem is reported at
150
+ * once — publish is a manual step, and a parser that stops at the first key
151
+ * makes it N round trips. */
152
+ function parseAuthoredApp(raw) {
153
+ const manifest = parseAppManifest(raw);
154
+ if (!manifest.ok) return {
155
+ ok: false,
156
+ problems: [manifest.kind === "missing" ? "app.json is missing" : manifest.detail]
157
+ };
158
+ const parsed = AuthoredAppZ.safeParse(JSON.parse(raw));
159
+ if (!parsed.success) return {
160
+ ok: false,
161
+ problems: authoredProblems(parsed.error)
162
+ };
163
+ return {
164
+ ok: true,
165
+ app: parsed.data
166
+ };
167
+ }
168
+ /** zod issues as one actionable line each: `public.submit.responses.auth: …`. */
169
+ function authoredProblems(error) {
170
+ return error.issues.map((issue) => {
171
+ return `${issue.path.length > 0 ? issue.path.join(".") : "app.json"}: ${issue.message}`;
172
+ });
173
+ }
174
+ //#endregion
175
+ //#region src/collection/server/publishProject.ts
176
+ /** The document id under `apps/{aid}/config`. One document, named, rather than
177
+ * a spread of them: a second public document is a second thing to keep in
178
+ * step, and nothing yet needs one. */
179
+ var PUBLIC_CONFIG_DOC = "public";
180
+ /** Drop keys whose value is `undefined`. Firestore rejects an undefined field
181
+ * value outright, and `"k" in c` — which every optional key in the rules is
182
+ * read through — must mean "the author declared it". */
183
+ function compact(entries) {
184
+ return Object.fromEntries(Object.entries(entries).filter(([, value]) => value !== void 0));
185
+ }
186
+ /** ISO → epoch millis, the one conversion the rules cannot do for themselves.
187
+ * The caller has already refused an unparseable string (the authored parser
188
+ * requires `z.iso.datetime()`), so a NaN here would be a programming error;
189
+ * it is still checked, because a NaN written to Firestore fails closed in the
190
+ * same silent way an ISO string does. */
191
+ function windowMillis(window) {
192
+ if (!window) return void 0;
193
+ const out = {};
194
+ if (window.from !== void 0) out.fromMs = Date.parse(window.from);
195
+ if (window.until !== void 0) out.untilMs = Date.parse(window.until);
196
+ if (Object.values(out).some((value) => !Number.isFinite(value))) throw new Error(`publish: window bound is not a parseable timestamp (${JSON.stringify(window)})`);
197
+ return Object.keys(out).length > 0 ? out : void 0;
198
+ }
199
+ /** One `public.submit[cid]`, with its window lowered. Everything else passes
200
+ * through: the rules read these keys by the names the author wrote. */
201
+ function projectSubmit(submit) {
202
+ const { window, ...rest } = submit;
203
+ return compact({
204
+ ...rest,
205
+ window: windowMillis(window)
206
+ });
207
+ }
208
+ /** The roster's addresses as a set, in a stable order.
209
+ *
210
+ * Sorted so two publishes of the same declaration produce the same document
211
+ * — idempotence is a property this step is tested for, and Firestore compares
212
+ * arrays by ORDER. `membersConsistent()` compares as sets and would accept
213
+ * any order; the test that would notice is the one asserting a second publish
214
+ * changes nothing but `publishedAt`. */
215
+ function memberEmailsOf(members) {
216
+ return Object.keys(members).sort();
217
+ }
218
+ /** The previous document, kept for rollback, with its OWN `previousPublished`
219
+ * stripped.
220
+ *
221
+ * One level, deliberately. Chaining would make every publish carry the entire
222
+ * history of the app inside a single document, which grows without bound and
223
+ * meets Firestore's 1 MiB document limit as a permission-shaped failure at
224
+ * some unpredictable publish. One level answers the question rollback
225
+ * actually asks — "put back what was there before I broke it" — and the
226
+ * further history is in git, which is where the declaration came from. */
227
+ function previousOf(existing) {
228
+ if (!existing) return void 0;
229
+ const { previousPublished: __dropped, ...rest } = existing;
230
+ return rest;
231
+ }
232
+ /** Project the authored declaration into the documents publish writes.
233
+ *
234
+ * `existing` is the app document as it is in Firestore right now, or null on
235
+ * a first publish. Two things need it, both required by the rules:
236
+ * - `owner` must be UNCHANGED on update. Re-stamping the publisher's uid
237
+ * would be refused for any app whose owner ever signed in as a different
238
+ * account, and would silently transfer ownership if it were not.
239
+ * - `previousPublished` is that document, so a rollback has something to
240
+ * put back.
241
+ *
242
+ * Pure: no clock, no filesystem, no Firestore. Everything variable arrives as
243
+ * a parameter, which is what makes the conversion table testable as a table. */
244
+ function projectApp(authored, schemas, stamp, existing) {
245
+ const owner = typeof existing?.owner === "string" ? existing.owner : stamp.uid;
246
+ const submit = Object.fromEntries(Object.entries(authored.public?.submit ?? {}).map(([cid, spec]) => [cid, projectSubmit(spec)]));
247
+ const publicBlock = authored.public ? compact({
248
+ enabled: authored.public.enabled,
249
+ read: authored.public.read,
250
+ submit: Object.keys(submit).length > 0 ? submit : void 0
251
+ }) : void 0;
252
+ const app = compact({
253
+ aid: authored.aid,
254
+ name: authored.name,
255
+ owner,
256
+ members: authored.members,
257
+ memberEmails: memberEmailsOf(authored.members),
258
+ collections: authored.collections,
259
+ participantRead: authored.participantRead,
260
+ public: publicBlock,
261
+ publishedAt: stamp.publishedAt,
262
+ publishedBy: stamp.email,
263
+ publishedCommit: stamp.commit,
264
+ publishedDirty: stamp.dirty === true ? true : void 0,
265
+ previousPublished: previousOf(existing)
266
+ });
267
+ const config = {
268
+ enabled: authored.public?.enabled === true,
269
+ read: authored.public?.read ?? [],
270
+ submit,
271
+ publishedAt: stamp.publishedAt
272
+ };
273
+ if (authored.name !== void 0) config.name = authored.name;
274
+ return {
275
+ app,
276
+ schemas: schemas.map(({ cid, schema }) => ({
277
+ cid,
278
+ doc: schemaDoc(schema, stamp)
279
+ })),
280
+ config
281
+ };
282
+ }
283
+ /** Keys on the app document that publish owns: what is PUBLIC right now, and
284
+ * the rule-facing configuration anonymous access is judged against. Deploy
285
+ * carries them through from the existing document and never authors them. */
286
+ var PUBLISH_OWNED_KEYS = [
287
+ "public",
288
+ "collections",
289
+ "participantRead",
290
+ "publishedAt",
291
+ "publishedBy",
292
+ "publishedCommit",
293
+ "publishedDirty",
294
+ "previousPublished"
295
+ ];
296
+ var isPublishOwned = (key) => PUBLISH_OWNED_KEYS.includes(key);
297
+ function projectDeploy(authored, schemas, stamp, existing) {
298
+ const { app } = projectApp(authored, schemas, stamp, existing);
299
+ const deployed = Object.fromEntries(Object.entries(app).filter(([key]) => !isPublishOwned(key)));
300
+ for (const key of PUBLISH_OWNED_KEYS) {
301
+ const live = existing?.[key];
302
+ if (live !== void 0) deployed[key] = live;
303
+ }
304
+ deployed.deployedAt = stamp.publishedAt;
305
+ deployed.deployedBy = stamp.email;
306
+ if (stamp.commit !== void 0) deployed.deployedCommit = stamp.commit;
307
+ return {
308
+ app: deployed,
309
+ staging: schemas.map(({ cid, schema }) => ({
310
+ cid,
311
+ doc: stagedDoc(schema, stamp, authored, cid)
312
+ }))
313
+ };
314
+ }
315
+ /** One staged schema document, carrying this cid's rule-facing configuration
316
+ * alongside the schema so publish can promote them together. */
317
+ function stagedDoc(schema, stamp, authored, cid) {
318
+ const doc = {
319
+ publishedSchema: schema,
320
+ deployedAt: stamp.publishedAt,
321
+ deployedBy: stamp.email
322
+ };
323
+ const config = authored.collections?.[cid];
324
+ if (config !== void 0) doc.config = config;
325
+ if (authored.participantRead?.includes(cid) === true) doc.participantRead = true;
326
+ if (stamp.commit !== void 0) doc.deployedCommit = stamp.commit;
327
+ return doc;
328
+ }
329
+ /** The rule-facing configuration to promote, read from the STAGED documents
330
+ * rather than from the manifest as it reads right now.
331
+ *
332
+ * Otherwise: deploy revision A, edit `app.json` to revision B, publish — and
333
+ * the promoted schema is A's while the authorization behaviour is B's, a
334
+ * combination nobody exercised through `/staging/{aid}`.
335
+ *
336
+ * (`public` is deliberately NOT part of this: it is not staged, because it is
337
+ * the decision being made AT publish rather than something under test.) */
338
+ function stagedRuleConfig(staged) {
339
+ const entries = [];
340
+ const participantRead = [];
341
+ for (const entry of staged) {
342
+ const { config } = entry.doc;
343
+ if (config !== void 0) entries.push([entry.cid, config]);
344
+ if (entry.doc.participantRead === true) participantRead.push(entry.cid);
345
+ }
346
+ return {
347
+ collections: entries.length > 0 ? Object.fromEntries(entries) : void 0,
348
+ participantRead: participantRead.length > 0 ? participantRead : void 0
349
+ };
350
+ }
351
+ function projectPublish(authored, staged, stamp, existing) {
352
+ const { app, config } = projectApp(authored, [], stamp, existing);
353
+ const staging = stagedRuleConfig(staged);
354
+ app.collections = staging.collections;
355
+ app.participantRead = staging.participantRead;
356
+ const published = Object.fromEntries(Object.entries(existing ?? app).filter(([key]) => !isPublishOwned(key)));
357
+ for (const key of PUBLISH_OWNED_KEYS) if (key !== "public" && app[key] !== void 0) published[key] = app[key];
358
+ const publicBlock = app.public;
359
+ return {
360
+ app: published,
361
+ config,
362
+ public: isRecord(publicBlock) ? publicBlock : void 0
363
+ };
364
+ }
365
+ /** Re-stamp a staged schema document as it is promoted to `collections/{cid}`.
366
+ *
367
+ * The stamp answers "which version is PUBLIC right now, and who made it so",
368
+ * so it is written by the operation that changes the answer — publish — not
369
+ * carried over from the deploy that staged it. */
370
+ function promoteSchema(staged, stamp) {
371
+ const doc = {
372
+ publishedSchema: staged.publishedSchema,
373
+ publishedAt: stamp.publishedAt,
374
+ publishedBy: stamp.email
375
+ };
376
+ if (stamp.commit !== void 0) doc.publishedCommit = stamp.commit;
377
+ return doc;
378
+ }
379
+ /** One published schema document. Written key by key rather than through
380
+ * `compact`, so the declared type is the type — an optional commit is the
381
+ * only variable part. */
382
+ function schemaDoc(schema, stamp) {
383
+ const doc = {
384
+ publishedSchema: schema,
385
+ publishedAt: stamp.publishedAt,
386
+ publishedBy: stamp.email
387
+ };
388
+ if (stamp.commit !== void 0) doc.publishedCommit = stamp.commit;
389
+ return doc;
390
+ }
391
+ /** The app documents' parent path — the `FirestoreDocs` seam takes a
392
+ * collection path plus a document id, and the app document's id is the aid. */
393
+ var APPS_COLLECTION = "apps";
394
+ /** The collection (schema) documents' parent path — what the PUBLIC page
395
+ * reads, written only by publish (promotion). */
396
+ var appSchemasPath = (aid) => `apps/${aid}/collections`;
397
+ /** The URL-slug reservations — `appSlugs/{slug}` → `{ aid, published }`.
398
+ *
399
+ * A TOP-LEVEL collection, not a field on the app: the public page resolves a
400
+ * slug to an aid BEFORE it can read anything under `apps/{aid}`, and a slug
401
+ * has to be claimable atomically (create-if-absent) so two apps cannot hold
402
+ * the same URL.
403
+ *
404
+ * `published` is what makes the reservation invisible until publish. The slug
405
+ * is human-readable, so a readable reservation would let anyone guess the URL
406
+ * and get the aid — and the aid is the `/staging/{aid}` entrance. The rule is
407
+ * `allow read: if resource.data.published == true`, which needs no `get()` and
408
+ * so costs nothing against the rules' expression budget. */
409
+ var APP_SLUGS_COLLECTION = "appSlugs";
410
+ var appSlugDoc = (aid, published) => ({
411
+ aid,
412
+ published
413
+ });
414
+ /** The staged schema documents' parent path — what `/staging/{aid}` reads,
415
+ * written by deploy. A separate DOCUMENT rather than a field beside
416
+ * `publishedSchema`, because the rules cannot hide a field: anything inside a
417
+ * document the public page may read is public. */
418
+ var appStagingPath = (aid) => `apps/${aid}/staging`;
419
+ /** The public-config documents' parent path. */
420
+ var appConfigPath = (aid) => `apps/${aid}/config`;
421
+ //#endregion
422
+ //#region src/collection/server/publishChecks.ts
423
+ /** Does this submit declaration bind a record to the submitter's identity?
424
+ *
425
+ * The condition for requiring `submitOnly`, and deliberately NOT "declares an
426
+ * `audience`": `audience` appears only in the rules' public-create branch, so
427
+ * an owner or editor never meets it and can add records freely. `immutable`
428
+ * is the wrong condition too — a survey's responses are not immutable and
429
+ * can be padded exactly the same way.
430
+ *
431
+ * What these four have in common is that each one makes the record MEAN "the
432
+ * person who submitted it said this": a per-uid id, a per-uid+field id, a
433
+ * row stamped with the submitter's verified address, or a submission
434
+ * restricted to a named participant. A record created through the writer
435
+ * branch carries the same shape and none of that meaning. */
436
+ function bindsSubmitterIdentity(submit) {
437
+ return submit.idFrom === "auth.uid" || submit.idFrom === "auth.uid+field" || submit.emailField !== void 0 || submit.audience === "participant";
438
+ }
439
+ /** The fields a rule actually CHECKS the value of, for one collection.
440
+ *
441
+ * `keyFields` pins a value against a declared set, `gateOn.match` pins it
442
+ * against the session's current question, and the status field is pinned by
443
+ * the transition machine. An aggregation grouped by anything else is grouped
444
+ * by a field any submitter may write anything into — so the published
445
+ * aggregate is whatever the noisiest respondent decided it should be. */
446
+ function checkedFields(collection, submit) {
447
+ const fields = /* @__PURE__ */ new Set();
448
+ for (const keyField of submit?.validate?.keyFields ?? []) fields.add(keyField.field);
449
+ if (submit?.gateOn) fields.add(submit.gateOn.match);
450
+ if (collection?.statusField) fields.add(collection.statusField);
451
+ return fields;
452
+ }
453
+ /** INVARIANT 1 — a submission bound to its submitter needs `submitOnly`. */
454
+ function submitOnlyProblems(app) {
455
+ const problems = [];
456
+ for (const [cid, submit] of Object.entries(app.public?.submit ?? {})) {
457
+ if (!bindsSubmitterIdentity(submit)) continue;
458
+ if (app.collections?.[cid]?.submitOnly === true) continue;
459
+ problems.push(`collections.${cid}.submitOnly must be true: public.submit.${cid} binds each record to its submitter (${identityBindings(submit).join(", ")}), so a record created any other way would carry that meaning without having earned it. Without submitOnly the rules let an owner or editor write rows directly into ${cid}.`);
460
+ }
461
+ return problems;
462
+ }
463
+ function identityBindings(submit) {
464
+ const bindings = [];
465
+ if (submit.idFrom === "auth.uid" || submit.idFrom === "auth.uid+field") bindings.push(`idFrom: "${submit.idFrom}"`);
466
+ if (submit.emailField !== void 0) bindings.push(`emailField: "${submit.emailField}"`);
467
+ if (submit.audience === "participant") bindings.push(`audience: "participant"`);
468
+ return bindings;
469
+ }
470
+ /** INVARIANT 2 — every aggregation key is a field some rule checks. */
471
+ function aggregateProblems(app) {
472
+ const problems = [];
473
+ for (const [cid, collection] of Object.entries(app.collections ?? {})) {
474
+ const keys = collection.aggregate?.by;
475
+ if (!keys) continue;
476
+ const checked = checkedFields(collection, app.public?.submit?.[cid]);
477
+ const loose = keys.filter((field) => !checked.has(field));
478
+ const spelled = loose.map((field) => `'${field}'`).join(", ");
479
+ if (loose.length > 0) problems.push(`collections.${cid}.aggregate.by names ${spelled}, which no rule checks the value of. An aggregation key must appear in public.submit.${cid}.validate.keyFields, in gateOn.match, or be the statusField — otherwise a submitter chooses their own bucket and the published aggregate is not a count of anything.`);
480
+ }
481
+ return problems;
482
+ }
483
+ /** INVARIANT 3 — `auth: "verifiedEmail"` only.
484
+ *
485
+ * A product decision, not a rules limitation: the rules keep all three stages
486
+ * and the emulator tests keep exercising them, because deleting a stage from
487
+ * the rules turns a change of mind into a cross-repo deploy. Publish is where
488
+ * the current decision is expressed, and it is one line to move. */
489
+ function authProblems(app) {
490
+ return Object.entries(app.public?.submit ?? {}).filter(([, submit]) => submit.auth !== "verifiedEmail").map(([cid, submit]) => `public.submit.${cid}.auth is "${submit.auth}": only "verifiedEmail" may be published. The rules still implement "none" and "anonymous" — this is a product decision, and lifting it is a change here, not a rules deploy.`);
491
+ }
492
+ /** INVARIANT 5 — a mail transition's origins and destination must be disjoint.
493
+ *
494
+ * Overlap means the same write can satisfy the same template twice over, and
495
+ * the deterministic mail id is the only other thing stopping a duplicate
496
+ * send. The rules also require the status to have CHANGED, so an overlapping
497
+ * declaration is not merely redundant: `from` containing `to` is a transition
498
+ * that can never fire, which is a mail nobody ever receives. */
499
+ function mailProblems(app) {
500
+ return Object.entries(app.collections ?? {}).flatMap(([cid, collection]) => collectionMailProblems(cid, collection));
501
+ }
502
+ function collectionMailProblems(cid, collection) {
503
+ const { mail } = collection;
504
+ if (!mail) return [];
505
+ const problems = [];
506
+ if (!collection.statusField) problems.push(`collections.${cid}.mail needs collections.${cid}.statusField: the rules read the status before and after the write to decide the mail is warranted.`);
507
+ for (const [template, transition] of Object.entries(mail.on)) problems.push(...templateMailProblems(cid, collection, template, transition));
508
+ return problems;
509
+ }
510
+ function templateMailProblems(cid, collection, template, transition) {
511
+ const problems = [];
512
+ if (transition.from.includes(transition.to)) problems.push(`collections.${cid}.mail.on.${template} lists "${transition.to}" in both \`from\` and \`to\`. The rules require the status to CHANGE in the same write, so this template can never send.`);
513
+ const allowed = collection.transitions;
514
+ if (allowed) {
515
+ const unreachable = transition.from.filter((from) => !(allowed[from] ?? []).includes(transition.to));
516
+ const spelled = unreachable.map((from) => `'${from}' -> '${transition.to}'`).join(", ");
517
+ if (unreachable.length > 0) problems.push(`collections.${cid}.mail.on.${template} sends on ${spelled}, which collections.${cid}.transitions does not allow. The record write is refused first, so the mail never fires.`);
518
+ }
519
+ return problems;
520
+ }
521
+ /** INVARIANTS 6 and 7 — the window is a real interval, and `keyFields` fits
522
+ * the unrolled check in the rules. */
523
+ function submitShapeProblems(app) {
524
+ return Object.entries(app.public?.submit ?? {}).flatMap(([cid, submit]) => [...windowProblems(cid, submit), ...keyFieldCountProblems(cid, submit)]);
525
+ }
526
+ function windowProblems(cid, submit) {
527
+ const { window } = submit;
528
+ if (window?.from === void 0 || window.until === void 0) return [];
529
+ if (Date.parse(window.until) > Date.parse(window.from)) return [];
530
+ return [`public.submit.${cid}.window closes at or before it opens (${window.from} -> ${window.until}): nothing could ever be submitted.`];
531
+ }
532
+ function keyFieldCountProblems(cid, submit) {
533
+ const keyFields = submit.validate?.keyFields ?? [];
534
+ if (keyFields.length <= 2) return [];
535
+ return [`public.submit.${cid}.validate.keyFields declares ${keyFields.length}; the rules check at most 2. Rules have no iteration, so the check is unrolled — a third would be published and never enforced.`];
536
+ }
537
+ /** The fail-closed traps: declarations the rules read together, where the
538
+ * missing half denies every write instead of loosening one. */
539
+ function coherenceProblems(app) {
540
+ const fromSubmits = Object.entries(app.public?.submit ?? {}).flatMap(([cid, submit]) => submitCoherenceProblems(app, cid, submit));
541
+ const fromCollections = Object.entries(app.collections ?? {}).flatMap(([cid, collection]) => gateCoherenceProblems(cid, collection));
542
+ return [...fromSubmits, ...fromCollections];
543
+ }
544
+ /** `initialStatus` is read together with the collection's `statusField` and
545
+ * with `createFields`; miss either and every submission is refused. */
546
+ function statusCoherenceProblems(cid, submit, collection) {
547
+ if (submit.initialStatus === void 0) return [];
548
+ if (!collection?.statusField) return [`public.submit.${cid}.initialStatus needs collections.${cid}.statusField: the rules look the status up by that name, and refuse every submission without it.`];
549
+ if (new Set(submit.createFields).has(collection.statusField)) return [];
550
+ return [`public.submit.${cid}.createFields must include "${collection.statusField}": a submission may carry ONLY the createFields, and the rules also require the status field to be present and equal to initialStatus. As written, every submission is refused.`];
551
+ }
552
+ /** Every field a RULE reads off a submitted record, other than the status
553
+ * field (which `statusCoherenceProblems` words for itself).
554
+ *
555
+ * `emailField` and `idField` belong here for exactly the reason `required`
556
+ * and `keyFields` do, and forgetting them was the same oversight twice: the
557
+ * rules read `request.resource.data[s.emailField]` and rebuild the document
558
+ * id from `s.idField`, while `hasOnly(createFields)` decides what a
559
+ * submission may carry at all. A field in one list and not the other is a
560
+ * contradiction the submitter cannot resolve — including it is refused,
561
+ * omitting it fails the check. */
562
+ function ruleReadFields(submit) {
563
+ const fields = [];
564
+ if (submit.emailField !== void 0) fields.push({
565
+ field: submit.emailField,
566
+ why: `public.submit.<cid>.emailField — the rules compare it to the submitter's verified address`
567
+ });
568
+ if (submit.idFrom === "auth.uid+field" && submit.idField !== void 0) fields.push({
569
+ field: submit.idField,
570
+ why: `public.submit.<cid>.idField — the rules rebuild the document id from it`
571
+ });
572
+ return fields;
573
+ }
574
+ /** A checked field a submission is not allowed to carry can never be
575
+ * satisfied: carrying it fails `hasOnly`, omitting it fails the check. */
576
+ function createFieldProblems(cid, submit) {
577
+ const createFields = new Set(submit.createFields);
578
+ const ruleRead = ruleReadFields(submit).filter((entry) => !createFields.has(entry.field)).map((entry) => `public.submit.${cid}.createFields must include "${entry.field}" (${entry.why.replace("<cid>", cid)}): a submission may carry only the createFields, so as written every submission is refused whether or not it carries the field.`);
579
+ const required = (submit.validate?.required ?? []).filter((field) => !createFields.has(field)).map((field) => `public.submit.${cid}.validate.required names "${field}", which is not in createFields: a submission may carry only the createFields, so the requirement can never be met.`);
580
+ const keyFields = (submit.validate?.keyFields ?? []).filter((keyField) => !createFields.has(keyField.field)).map((keyField) => `public.submit.${cid}.validate.keyFields checks "${keyField.field}", which is not in createFields: a submission carrying it is refused, and one omitting it fails the check.`);
581
+ return [
582
+ ...ruleRead,
583
+ ...required,
584
+ ...keyFields
585
+ ];
586
+ }
587
+ function submitCoherenceProblems(app, cid, submit) {
588
+ const collection = app.collections?.[cid];
589
+ const problems = [...statusCoherenceProblems(cid, submit, collection), ...createFieldProblems(cid, submit)];
590
+ if (submit.idFrom === "auth.uid+field" && submit.idField === void 0) problems.push(`public.submit.${cid}.idFrom is "auth.uid+field" but no idField is declared: the rules rebuild the document id from that field and refuse every create.`);
591
+ if ((submit.selfUpdate !== void 0 || submit.selfTransitions !== void 0) && !collection?.statusField) problems.push(`public.submit.${cid}.selfUpdate / selfTransitions are declared per CURRENT STATUS, but collections.${cid} declares no statusField: the rules read the current status first and refuse every self-edit without it.`);
592
+ if (submit.audience === "participant" && Object.keys(app.members).length === 0) problems.push(`public.submit.${cid}.audience is "participant" but the roster is empty: the rules resolve the submitter's role from members, so every submission is refused.`);
593
+ return problems;
594
+ }
595
+ /** The staged reveal reads its flag off the PARENT record, so the path to that
596
+ * parent is not optional decoration — without it the gate never opens. */
597
+ function gateCoherenceProblems(cid, collection) {
598
+ if (collection.revealGated !== true) return [];
599
+ if (collection.gatedFrom !== void 0 && collection.revealBy !== void 0) return [];
600
+ return [`collections.${cid}.revealGated needs both gatedFrom and revealBy: the flag is read off the PARENT record, and without the path the gate never opens.`];
601
+ }
602
+ /** The publisher must be able to write what they are about to write.
603
+ *
604
+ * On a first publish the rules require the creator to name themselves owner,
605
+ * in the roster, under `'*'`. Getting this wrong produces a bare permission
606
+ * error from Firestore with nothing in it about rosters — worth one line
607
+ * here instead. */
608
+ function publisherProblems(app, publisherEmail) {
609
+ if (app.members[publisherEmail]?.["*"] === "owner") return [];
610
+ return [`members must give you app-wide owner: add "${publisherEmail}": { "*": "owner" }. The rules require the publisher to hold that role (and to name themselves owner when the app is first created); otherwise the write is refused with no explanation.`];
611
+ }
612
+ /** Every cid the declaration mentions must be a collection that exists.
613
+ *
614
+ * A typo'd cid is not an error anywhere else: the app document simply carries
615
+ * a configuration for a collection nobody publishes, and the collection the
616
+ * author meant is published with no configuration at all — i.e. with the
617
+ * status machine and the submit path silently absent. */
618
+ function unknownCidProblems(app, collections) {
619
+ const known = new Set(collections.map((collection) => collection.cid));
620
+ return [
621
+ ["collections", Object.keys(app.collections ?? {})],
622
+ ["public.read", app.public?.read ?? []],
623
+ ["public.submit", Object.keys(app.public?.submit ?? {})],
624
+ ["participantRead", app.participantRead ?? []]
625
+ ].flatMap(([where, cids]) => cids.filter((cid) => !known.has(cid)).map((cid) => `${where} names '${cid}', which is not a shared collection in this repository. Shared collections here: ${known.size > 0 ? [...known].sort().join(", ") : "(none - a schema needs storage.type \"firestore\")"}.`));
626
+ }
627
+ /** Everything publish refuses, as lines the author can act on.
628
+ *
629
+ * All of them, every time. Publish is a manual step with a human waiting on
630
+ * it; stopping at the first problem turns one review into five. */
631
+ function publishProblems(app, collections, publisherEmail) {
632
+ return [
633
+ ...unknownCidProblems(app, collections),
634
+ ...publisherProblems(app, publisherEmail),
635
+ ...submitOnlyProblems(app),
636
+ ...aggregateProblems(app),
637
+ ...authProblems(app),
638
+ ...mailProblems(app),
639
+ ...submitShapeProblems(app),
640
+ ...coherenceProblems(app),
641
+ ...primaryKeyProblems(app, collections)
642
+ ];
643
+ }
644
+ /** A public submission must NOT be allowed to name its own primary key.
645
+ *
646
+ * The rules constrain the DOCUMENT ID (`idFrom`) and cannot constrain the
647
+ * value of a field — nothing compares `request.resource.data[primaryKey]`
648
+ * with the path being written. So a submit path that accepts the primary key
649
+ * as a `createField` lets a submitter write at their one permitted document
650
+ * id while CLAIMING another record's identity, or a duplicate.
651
+ *
652
+ * It is refused rather than tolerated because there is nothing for the field
653
+ * to do: `firestoreStore` takes a shared record's identity from the document
654
+ * id and overwrites the field on read, so a submitted value is either equal
655
+ * to the id (noise) or a lie (silently discarded). Publishing a form field
656
+ * whose value is thrown away is worse than not having it — the author will
657
+ * believe submitters choose their ids.
658
+ *
659
+ * This is the second answer to the same question. The first was the reverse —
660
+ * REQUIRE the key, because a record without one was rejected by every reader
661
+ * — and it was right about the symptom and wrong about the cure: the identity
662
+ * belongs to the id the rules can pin, not to a field they cannot. */
663
+ function primaryKeyProblems(app, collections) {
664
+ const primaryKeyOf = new Map(collections.map((collection) => [collection.cid, collection.primaryKey]));
665
+ return Object.entries(app.public?.submit ?? {}).flatMap(([cid, submit]) => {
666
+ const primaryKey = primaryKeyOf.get(cid);
667
+ if (primaryKey === void 0 || !submit.createFields.includes(primaryKey)) return [];
668
+ return [`public.submit.${cid}.createFields must NOT include "${primaryKey}", the schema's primaryKey: the rules can pin the document id but not the value of a field, so a submitter could write at their own id while claiming another record's. A shared record's identity is its document id — the store fills the field from it, and a submitted value is either the same thing or a lie that is thrown away.`];
669
+ });
670
+ }
671
+ //#endregion
13
672
  //#region src/collection/server/skillAssets.ts
14
673
  /** Read a collection's custom-view HTML, path-safely. `viewFile` is a
15
674
  * schema-validated `views/*.html` path, resolved with realpath containment.
@@ -469,6 +1128,18 @@ function firstRecordProblem(record, schema, tier) {
469
1128
  }
470
1129
  //#endregion
471
1130
  //#region src/collection/server/validate.ts
1131
+ /** Don't flood the result; the first batch is enough to act on. Exported
1132
+ * because a caller that REPORTS a count has to know the count is a floor —
1133
+ * `publish` presents a full batch as "at least N" rather than as a total. */
1134
+ var MAX_RECORD_ISSUES = 25;
1135
+ /** The `file` of the pseudo-issue reported when the backend could not be read
1136
+ * at all.
1137
+ *
1138
+ * Exported because it is a DIFFERENT KIND of answer from "this record is
1139
+ * invalid", and a caller that treats the two alike gets it wrong in the
1140
+ * direction that matters: `publish` lets the user override invalid records,
1141
+ * and overriding this one would mean publishing without ever having looked. */
1142
+ var STORE_UNREADABLE = "(store)";
472
1143
  var MAX_ISSUES = 25;
473
1144
  /** Read every `<id>.json` under the collection's dataDir and report the
474
1145
  * ones that won't load or violate the schema. An empty list means every
@@ -514,7 +1185,7 @@ async function validateStoreRecords(collection, opts) {
514
1185
  items = await storeFor(collection, { workspaceRoot: opts.workspaceRoot }).list();
515
1186
  } catch (err) {
516
1187
  return [{
517
- file: "(store)",
1188
+ file: STORE_UNREADABLE,
518
1189
  problem: `records could not be read from the storage backend: ${err instanceof Error ? err.message : String(err)}`
519
1190
  }];
520
1191
  }
@@ -2067,6 +2738,6 @@ function makeManageCollectionTool(deps = {}) {
2067
2738
  };
2068
2739
  }
2069
2740
  //#endregion
2070
- export { buildCollectionActionSeedPrompt as A, validateRecordObject as C, enrichItems as D, runCollectionQuery as E, readCustomViewHtml as M, readCustomViewI18n as N, runQueryOverRows as O, readSkillTemplate as P, validateCollectionRecords as S, recordFieldProblem as T, computeCollectionIcon as _, deleteCollection as a, applyMutateAction as b, computeSuccessor as c, isTriggerDue as d, maybeSpawnSuccessor as f, ONE_SECOND_MS as g, successorId as h, deleteCustomView as i, promptPathsFor as j, buildActionSeedPrompt as k, daysInMonth as l, resolveEvery as m, MAX_UNSELECTIVE_ITEMS as n, deleteCollectionRefusalMessage as o, parseCivil as p, makeManageCollectionTool as r, advanceTriggerDate as s, MAX_SCHEMA_ISSUES as t, formatCivil as u, buildWorkspaceOntology as v, compileRecordZ as w, firstMutateParamProblem as x, schemaRelations as y };
2741
+ export { parseAuthoredApp as $, runQueryOverRows as A, APP_SLUGS_COLLECTION as B, STORE_UNREADABLE as C, recordFieldProblem as D, compileRecordZ as E, readCustomViewI18n as F, appStagingPath as G, appConfigPath as H, readSkillTemplate as I, projectPublish as J, projectApp as K, bindsSubmitterIdentity as L, buildCollectionActionSeedPrompt as M, promptPathsFor as N, runCollectionQuery as O, readCustomViewHtml as P, AuthoredAppZ as Q, publishProblems as R, MAX_RECORD_ISSUES as S, validateRecordObject as T, appSchemasPath as U, PUBLIC_CONFIG_DOC as V, appSlugDoc as W, stagedRuleConfig as X, promoteSchema as Y, APP_ROLES as Z, computeCollectionIcon as _, deleteCollection as a, applyMutateAction as b, computeSuccessor as c, isTriggerDue as d, 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, projectDeploy 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, APPS_COLLECTION as z };
2071
2742
 
2072
- //# sourceMappingURL=server-DdHaX_Uu.js.map
2743
+ //# sourceMappingURL=server-BNe8XMtu.js.map