@mulmoclaude/core 4.1.0 → 4.2.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 (48) hide show
  1. package/assets/helps/collection-skills.md +19 -0
  2. package/assets/helps/error-recovery.md +63 -0
  3. package/dist/collection/core/calendarGrid.d.ts +2 -2
  4. package/dist/collection/core/serverTime.d.ts +93 -0
  5. package/dist/collection/index.cjs +9 -2
  6. package/dist/collection/index.d.ts +1 -0
  7. package/dist/collection/index.js +3 -3
  8. package/dist/collection/registry/server/index.cjs +2 -2
  9. package/dist/collection/registry/server/index.js +2 -2
  10. package/dist/collection/server/index.cjs +5 -3
  11. package/dist/collection/server/index.js +4 -4
  12. package/dist/collection/server/manageTool.d.ts +29 -0
  13. package/dist/collection-watchers/index.cjs +4 -4
  14. package/dist/collection-watchers/index.js +4 -4
  15. package/dist/{discovery-B9EESjHP.cjs → discovery-Dl9nRa-m.cjs} +38 -20
  16. package/dist/discovery-Dl9nRa-m.cjs.map +1 -0
  17. package/dist/{discovery-D88x2DmW.js → discovery-P64fgtuD.js} +38 -20
  18. package/dist/discovery-P64fgtuD.js.map +1 -0
  19. package/dist/feeds/index.cjs +2 -2
  20. package/dist/feeds/index.js +2 -2
  21. package/dist/feeds/server/index.cjs +4 -4
  22. package/dist/feeds/server/index.js +4 -4
  23. package/dist/google/index.cjs +2 -2
  24. package/dist/google/index.js +4 -4
  25. package/dist/google/index.js.map +1 -1
  26. package/dist/{ingestTypes-B312BwXg.js → ingestTypes-CIy1lHjf.js} +2 -2
  27. package/dist/{ingestTypes-B312BwXg.js.map → ingestTypes-CIy1lHjf.js.map} +1 -1
  28. package/dist/{ingestTypes-B4XfrUoq.cjs → ingestTypes-CaX7_s-K.cjs} +2 -2
  29. package/dist/{ingestTypes-B4XfrUoq.cjs.map → ingestTypes-CaX7_s-K.cjs.map} +1 -1
  30. package/dist/{itemId-DfTm08jm.js → itemId-BY2YPBkT.js} +200 -6
  31. package/dist/itemId-BY2YPBkT.js.map +1 -0
  32. package/dist/{itemId-Dzrr3vs5.cjs → itemId-CEQdK01z.cjs} +241 -5
  33. package/dist/itemId-CEQdK01z.cjs.map +1 -0
  34. package/dist/{promptSafety-CJ-sC1pX.js → promptSafety-Bp780ezk.js} +2 -2
  35. package/dist/{promptSafety-CJ-sC1pX.js.map → promptSafety-Bp780ezk.js.map} +1 -1
  36. package/dist/{promptSafety-CuczAN9d.cjs → promptSafety-C0BWDsfT.cjs} +2 -2
  37. package/dist/{promptSafety-CuczAN9d.cjs.map → promptSafety-C0BWDsfT.cjs.map} +1 -1
  38. package/dist/{server-dijB4BYv.js → server-C-Ngd3g2.js} +218 -14
  39. package/dist/server-C-Ngd3g2.js.map +1 -0
  40. package/dist/{server-DSrRdPvf.cjs → server-DZkN0GGh.cjs} +228 -12
  41. package/dist/server-DZkN0GGh.cjs.map +1 -0
  42. package/package.json +1 -1
  43. package/dist/discovery-B9EESjHP.cjs.map +0 -1
  44. package/dist/discovery-D88x2DmW.js.map +0 -1
  45. package/dist/itemId-DfTm08jm.js.map +0 -1
  46. package/dist/itemId-Dzrr3vs5.cjs.map +0 -1
  47. package/dist/server-DSrRdPvf.cjs.map +0 -1
  48. package/dist/server-dijB4BYv.js.map +0 -1
@@ -960,6 +960,25 @@ records through **`manageCollection`**, not raw file I/O:
960
960
  `mode: "merge"` with a partial row (`{ id, <changed fields> }`) when
961
961
  updating — the default upsert replaces the WHOLE record and would erase
962
962
  every optional field the row omits.
963
+ - **Rows a script generated — `putItems` with `itemsFile`.** When the rows
964
+ already exist as data rather than in your head (a generated schedule, an
965
+ imported set — anything past a few dozen records), have the script write
966
+ them to a JSON file **under the workspace** and pass its **absolute** path as
967
+ `itemsFile` instead of `items`. The host reads the file, so the rows never
968
+ pass through your context: a 540-row batch is one short tool call, not 80 KB
969
+ you transcribe. The rules:
970
+ - `items` and `itemsFile` are mutually exclusive — passing both is refused,
971
+ not merged.
972
+ - The path must be **absolute** (the tool runs in the host's server process,
973
+ whose working directory is not yours) and **inside the workspace** — write
974
+ the generated file there rather than to a system temp dir. It must be a real
975
+ file, not a symlink.
976
+ - The file must hold a **non-empty JSON array of objects**, at most 8 MiB.
977
+ - One call writes at most **1000 rows** — over that it is refused whole,
978
+ with nothing written, so split the file and call again.
979
+
980
+ Everything else is identical: same per-row validation, same `mode`, same
981
+ `{ written, rejected }` result.
963
982
  - **Read / list — `getItems`.** The only way to see host-computed `derived` /
964
983
  `toggle` / `embed` values (the stored JSON never contains them). Pass `ids`
965
984
  / `fields` on large collections to keep the result small — e.g.
@@ -1008,6 +1008,69 @@ Do NOT tell the user to re-check the spelling in `.env`, add the key
1008
1008
  again, or move it elsewhere; the file is already correct, and it is being
1009
1009
  read. The conflict is the whole problem.
1010
1010
 
1011
+ ## A `putItems` batch is too large to pass inline — use `itemsFile`, never the MCP bridge
1012
+
1013
+ ### Symptoms
1014
+
1015
+ - You generated records with a script (a month of booking slots, an imported
1016
+ CSV, anything past a few dozen rows) and the only way you can see to store
1017
+ them is to write every object into `manageCollection`'s `items` argument.
1018
+ - You start looking for the collection's data directory on disk, or for a way
1019
+ to hand the file to the tool.
1020
+ - The next step that suggests itself is spawning `server/mcp/bridge.mjs` (or
1021
+ the host's MCP server) yourself and speaking JSON-RPC to it from a script.
1022
+
1023
+ ### Cause
1024
+
1025
+ `items` takes the rows inline, so a 540-row batch means writing ~80 KB of JSON
1026
+ token by token — even when a script already produced exactly that JSON as a
1027
+ file. The judgement that the batch cannot go through `items` is correct; only
1028
+ the workaround is wrong.
1029
+
1030
+ ### Fix
1031
+
1032
+ Pass the file instead. `putItems` accepts **`itemsFile`** — an absolute path to
1033
+ a JSON file holding the array of record objects, read by the host:
1034
+
1035
+ ```jsonc
1036
+ { "action": "putItems", "slug": "slots", "mode": "create",
1037
+ "itemsFile": "/absolute/path/to/generated-slots.json" }
1038
+ ```
1039
+
1040
+ Write the generated file **under the workspace**. Paths outside it are refused:
1041
+ the host reads this file on your behalf, so an unconstrained path would let the
1042
+ tool reach host files you cannot otherwise see. Under a sandbox your workspace
1043
+ path is translated to the host's automatically — you pass the path you wrote to.
1044
+
1045
+ Rules that make it fail cleanly rather than silently:
1046
+
1047
+ - **Absolute paths only.** The tool runs in the host's SERVER process, whose
1048
+ working directory is not yours; a relative path is refused rather than
1049
+ resolved against some unrelated directory.
1050
+ - **Inside the workspace only**, and **not a symlink** — pass the real path of a
1051
+ regular file. Symlinks are refused rather than followed, contained or not.
1052
+ - **`items` or `itemsFile`, never both** — passing both is refused, not merged.
1053
+ - **1000 rows per call, max**, and the file itself at most 8 MiB. Over either,
1054
+ the call is refused WHOLE with nothing written; split the file and call again,
1055
+ so you never meet a half-filled collection.
1056
+
1057
+ Reading the refusal you got:
1058
+
1059
+ | Message | What it means |
1060
+ | --- | --- |
1061
+ | `must be an ABSOLUTE path` | You passed a relative path. Pass the full one. |
1062
+ | `must be inside the workspace` | The file is outside the workspace (or a symlink out of it). Regenerate it under the workspace. |
1063
+ | `is a symbolic link` | Symlinks are never followed. Pass the real path. |
1064
+ | `changed while it was being opened` | The file was replaced mid-call. Finish writing it, then call putItems. |
1065
+ | `grew while it was being read` | The file was still being written. Wait for the script to finish, then call putItems. |
1066
+ | `could not read \`itemsFile\`` | The host cannot see that path — the usual cause is a file written to a temp dir outside the mount. Write it under the workspace. |
1067
+ | `is not a regular file` | The path is a directory, device, or fifo. |
1068
+ | `could not be read as JSON` | The file exists and was read, but does not parse. This is YOUR file's shape, not a host problem — check the script that wrote it (a truncated write, a trailing comma, log output mixed into the file). |
1069
+ | `must hold a non-empty JSON array of record objects` | It parsed, but is `[]`, an object, or an array of scalars. The file must be `[{…}, {…}]` — the same row objects you would have passed as `items`. |
1070
+
1071
+ Do NOT spawn the MCP bridge yourself. A hand-written JSON-RPC client fails
1072
+ invisibly and can leave a partially written collection that nothing reports.
1073
+
1011
1074
  ## A tool your own instructions describe returns "No such tool available"
1012
1075
 
1013
1076
  ### Symptoms
@@ -43,8 +43,8 @@ export declare function parseIsoDateTime(value: unknown): {
43
43
  ymd: Ymd;
44
44
  minutes: number;
45
45
  } | null;
46
- /** Civil date from either a `YYYY-MM-DD` or a `YYYY-MM-DDTHH:MM` value, so the
47
- * month grid buckets date-only and datetime anchors alike. */
46
+ /** Civil date from a `YYYY-MM-DD`, a `YYYY-MM-DDTHH:MM`, or a server-stamped
47
+ * instant, so the month grid buckets all three alike. */
48
48
  export declare function dateOf(value: unknown): Ymd | null;
49
49
  /** Parse a free-form time-string field into start/end minutes-of-day.
50
50
  * Handles the common shapes in user data:
@@ -0,0 +1,93 @@
1
+ /** What a Firestore `Timestamp` looks like once it is only data. Duck-typed on
2
+ * purpose: this module is pure and imports no SDK, because it runs where the
3
+ * class is already gone — a page's payload, a JSON body, a test. */
4
+ export interface ServerTimeParts {
5
+ seconds: number;
6
+ nanoseconds: number;
7
+ }
8
+ /** Is this the canonical form, and a real instant?
9
+ *
10
+ * The shape check alone is not enough, and the gap is not theoretical: with it,
11
+ * `2026-02-30T00:00:00.000000000Z` passes, the record lint reports nothing, and
12
+ * `Date.parse` then normalises it to March 2 — so a write/read round trip
13
+ * silently moves the value. The round trip below is the check: a string is
14
+ * canonical only when re-formatting what it parses to gives it back. */
15
+ export declare const isCanonicalServerTime: (value: unknown) => value is string;
16
+ /** The canonical string for a (seconds, nanoseconds) pair, or null when the
17
+ * pair is not a real instant.
18
+ *
19
+ * Built from the SECONDS through `Date` and the nanoseconds as text, never by
20
+ * turning nanoseconds into a float — `605987654 / 1e9` cannot be represented
21
+ * exactly, and a rounding error here is a rank in the wrong place. */
22
+ export declare function serverTimeOf(parts: ServerTimeParts): string | null;
23
+ /** A stored server time in any of the shapes it reaches us in, as the
24
+ * canonical string — or null when the value is not one at all.
25
+ *
26
+ * The three shapes are the three trips a `Timestamp` can make: still an SDK
27
+ * instance (a host reading Firestore directly), structured-cloned (a page's
28
+ * payload), or JSON (`Timestamp.toJSON`, which tags itself). All three carry
29
+ * `seconds` and `nanoseconds`; the tag is not required, because the clone
30
+ * drops it. */
31
+ export declare function decodeServerTime(value: unknown): string | null;
32
+ /** The inverse: the parts a canonical string stands for, or null for anything
33
+ * else.
34
+ *
35
+ * This is what closes the write-back hole. A record is read (decoded), edited,
36
+ * and written back WHOLE — `putItems`' upsert replaces the document — so
37
+ * without an encode the stamp would go back as a string. The rules freeze that
38
+ * field by comparing `diff().affectedKeys()`, so the write is refused and the
39
+ * record can never be updated again. Dropping the field instead does not help:
40
+ * a removed key is an affected key too.
41
+ *
42
+ * Deliberately narrow. Only the EXACT canonical form is encoded, and that form
43
+ * is only ever produced by `decodeServerTime` — a human or an agent authoring
44
+ * a `datetime` writes the civil shape, which passes through as the string it
45
+ * is. So the codec is closed over its own output and cannot re-type somebody
46
+ * else's data. */
47
+ export declare function encodeServerTime(value: unknown): ServerTimeParts | null;
48
+ /** Epoch milliseconds for a canonical instant, for the places that can only
49
+ * hold a number (a sort key, a civil placement). LOSSY below the millisecond
50
+ * and knowingly so: whoever needs the full order compares the strings. */
51
+ export declare function serverTimeMillis(value: unknown): number | null;
52
+ /** The only part of a schema this codec reads.
53
+ *
54
+ * Structural rather than `CollectionSchema`, because the OTHER host has its own
55
+ * type for the same document (mulmoserver reads these records without going
56
+ * through this package's store) and neither side should have to adopt the
57
+ * other's declaration to call one function. */
58
+ export interface DeclaredFields {
59
+ fields: Record<string, {
60
+ type: string;
61
+ } | undefined>;
62
+ }
63
+ /** Every stored instant in a `datetime` field, as its canonical string; every
64
+ * other value untouched. One level deep, which is where a stamped field
65
+ * lives — the rules stamp a top-level key and nothing writes a server time
66
+ * into a table row. */
67
+ export declare function decodeRecordTimes(record: Record<string, unknown>, schema: DeclaredFields): Record<string, unknown>;
68
+ /** The write half, and it encodes ONLY what was stored as an instant.
69
+ *
70
+ * `previous` is the document as Firestore holds it right now. That is the only
71
+ * honest source of provenance here: a decoded stamp is a plain string by the
72
+ * time it comes back, indistinguishable from one an author typed or an import
73
+ * produced, and no amount of looking at the string can tell them apart. The
74
+ * declaration that pins the field lives in `app.json` under
75
+ * `public.submit.<cid>`, which this package reads only for `aid`
76
+ * (`../server/appManifest`, "SCOPE") — so the stored value is what is left, and
77
+ * it answers exactly the right question: was this field an instant before?
78
+ *
79
+ * What that buys, concretely: the frozen stamp goes back unchanged, so a
80
+ * whole-record write survives the rules; and a `datetime` field holding a
81
+ * canonical-looking string that was NEVER an instant — imported data, a value
82
+ * somebody typed — stays the string it is. The format alone would have
83
+ * re-typed it.
84
+ *
85
+ * `previous` is null for a create, where nothing was stored and nothing can
86
+ * therefore be preserved. That is correct rather than a gap: the rules require
87
+ * a created stamp to equal `request.time`, which no client can construct, so a
88
+ * create through this store never carries a valid one.
89
+ *
90
+ * `toStored` builds whatever the backend wants for an instant — a Firestore
91
+ * `Timestamp` — and is injected so this module stays free of the SDK: it also
92
+ * runs in a browser, where importing that would be a bundle. */
93
+ export declare function encodeRecordTimes(record: Record<string, unknown>, previous: Record<string, unknown> | null, toStored: (parts: ServerTimeParts) => unknown): Record<string, unknown>;
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_dist = require("../dist-CsgSfWwR.cjs");
3
- const require_itemId = require("../itemId-Dzrr3vs5.cjs");
4
- const require_promptSafety = require("../promptSafety-CuczAN9d.cjs");
3
+ const require_itemId = require("../itemId-CEQdK01z.cjs");
4
+ const require_promptSafety = require("../promptSafety-C0BWDsfT.cjs");
5
5
  const require_project = require("../project-C1ep9pvo.cjs");
6
6
  //#region src/collection/core/presentCollection.ts
7
7
  var TOOL_NAME = "presentCollection";
@@ -669,12 +669,16 @@ exports.completionCoveredByFieldChip = require_promptSafety.completionCoveredByF
669
669
  exports.dateOf = require_itemId.dateOf;
670
670
  exports.dateSortValue = dateSortValue;
671
671
  exports.daySlice = require_itemId.daySlice;
672
+ exports.decodeRecordTimes = require_itemId.decodeRecordTimes;
673
+ exports.decodeServerTime = require_itemId.decodeServerTime;
672
674
  exports.defangForPrompt = require_promptSafety.defangForPrompt;
673
675
  exports.deriveAll = require_promptSafety.deriveAll;
674
676
  exports.derivedSortValue = derivedSortValue;
675
677
  exports.draftToRecord = draftToRecord;
676
678
  exports.embedTargetId = require_itemId.embedTargetId;
677
679
  exports.emptyRow = emptyRow;
680
+ exports.encodeRecordTimes = require_itemId.encodeRecordTimes;
681
+ exports.encodeServerTime = require_itemId.encodeServerTime;
678
682
  exports.enumColorClasses = enumColorClasses;
679
683
  exports.enumSortValue = enumSortValue;
680
684
  exports.enumValueIndex = enumValueIndex;
@@ -690,6 +694,7 @@ exports.firstMissingRequiredField = firstMissingRequiredField;
690
694
  exports.firstUnknownDefault = require_promptSafety.firstUnknownDefault;
691
695
  exports.flagFieldValue = require_promptSafety.flagFieldValue;
692
696
  exports.generateUniqueId = require_itemId.generateUniqueId;
697
+ exports.isCanonicalServerTime = require_itemId.isCanonicalServerTime;
693
698
  exports.isFieldDrivenEvery = require_itemId.isFieldDrivenEvery;
694
699
  exports.isLocalCollectionKey = require_itemId.isLocalCollectionKey;
695
700
  exports.isReadOnlySchema = require_itemId.isReadOnlySchema;
@@ -729,6 +734,8 @@ exports.sameCollectionKey = require_itemId.sameCollectionKey;
729
734
  exports.scalarSortValue = scalarSortValue;
730
735
  exports.schemaDefaults = require_promptSafety.schemaDefaults;
731
736
  exports.selectDynamicRecord = require_promptSafety.selectDynamicRecord;
737
+ exports.serverTimeMillis = require_itemId.serverTimeMillis;
738
+ exports.serverTimeOf = require_itemId.serverTimeOf;
732
739
  exports.sharedCollectionKey = require_itemId.sharedCollectionKey;
733
740
  exports.shortHexId = require_itemId.shortHexId;
734
741
  exports.skillCommandSeed = skillCommandSeed;
@@ -11,6 +11,7 @@ export * from './core/enumColors';
11
11
  export * from './core/draft';
12
12
  export * from './core/actionVisible';
13
13
  export * from './core/backlinks';
14
+ export * from './core/serverTime';
14
15
  export * from './core/linkTargets';
15
16
  export * from './core/where';
16
17
  export * from './core/completion';
@@ -1,6 +1,6 @@
1
1
  import { t as errorMessage } from "../dist-D8zokgGo.js";
2
- import { A as isSafeSlug, B as fieldText, C as parseCollectionKeyId, D as SAFE_SLUG_PATTERN, E as SAFE_RECORD_ID_PATTERN, F as INGEST_KINDS, I as embedTargetId, L as isFieldDrivenEvery, M as AGENT_INGEST_KIND, N as COMPUTED_TYPES, O as generateUniqueId, P as FEED_SCHEDULES, R as isReadOnlySchema, S as localCollectionKeyOf, T as sharedCollectionKey, V as fieldTextOrNull, _ as collectionKeyId, a as bucketRecords, b as isSharedCollectionKey, c as dateOf, d as parseIsoDate, f as parseIsoDateTime, g as ymdKey, h as spanCoversDay, i as assignLanes, j as nextUniqueItemId, k as isSafeRecordId, l as daySlice, m as recordSpan, n as shortHexId, o as buildMonthGrid, p as parseTimeRange, r as MINUTES_PER_DAY, s as compareYmd, t as newItemId, u as monthAnchorDate, v as collectionKeyName, w as sameCollectionKey, x as isValidCollectionName, y as isLocalCollectionKey, z as storageKindFor } from "../itemId-DfTm08jm.js";
3
- import { C as actionVisible, D as fieldDefaultValue, E as whenMatches, O as firstUnknownDefault, S as viaMatches, T as fieldVisible, _ as uniqueRefTargets, a as evaluateDerived, b as projectBacklinkRow, c as selectDynamicRecord, d as flagFieldValue, f as itemIsDone, g as uniqueEmbedTargets, h as uniqueBacklinkSources, k as schemaDefaults, l as chipMatches, m as matchesWhere, n as deriveAll, o as firstDateField, p as toggleChecked, r as resolveRowRefs, s as resolveIcon, t as defangForPrompt, u as completionCoveredByFieldChip, v as backlinkRows, w as agentActionRunKey, x as rollupValue, y as coerceNumeric } from "../promptSafety-CJ-sC1pX.js";
2
+ import { A as parseCollectionKeyId, B as COMPUTED_TYPES, C as serverTimeOf, D as isSharedCollectionKey, E as isLocalCollectionKey, F as generateUniqueId, G as isReadOnlySchema, H as INGEST_KINDS, I as isSafeRecordId, J as fieldTextOrNull, K as storageKindFor, L as isSafeSlug, M as sharedCollectionKey, N as SAFE_RECORD_ID_PATTERN, O as isValidCollectionName, P as SAFE_SLUG_PATTERN, R as nextUniqueItemId, S as serverTimeMillis, T as collectionKeyName, U as embedTargetId, V as FEED_SCHEDULES, W as isFieldDrivenEvery, _ as decodeRecordTimes, a as bucketRecords, b as encodeServerTime, c as dateOf, d as parseIsoDate, f as parseIsoDateTime, g as ymdKey, h as spanCoversDay, i as assignLanes, j as sameCollectionKey, k as localCollectionKeyOf, l as daySlice, m as recordSpan, n as shortHexId, o as buildMonthGrid, p as parseTimeRange, q as fieldText, r as MINUTES_PER_DAY, s as compareYmd, t as newItemId, u as monthAnchorDate, v as decodeServerTime, w as collectionKeyId, x as isCanonicalServerTime, y as encodeRecordTimes, z as AGENT_INGEST_KIND } from "../itemId-BY2YPBkT.js";
3
+ import { C as actionVisible, D as fieldDefaultValue, E as whenMatches, O as firstUnknownDefault, S as viaMatches, T as fieldVisible, _ as uniqueRefTargets, a as evaluateDerived, b as projectBacklinkRow, c as selectDynamicRecord, d as flagFieldValue, f as itemIsDone, g as uniqueEmbedTargets, h as uniqueBacklinkSources, k as schemaDefaults, l as chipMatches, m as matchesWhere, n as deriveAll, o as firstDateField, p as toggleChecked, r as resolveRowRefs, s as resolveIcon, t as defangForPrompt, u as completionCoveredByFieldChip, v as backlinkRows, w as agentActionRunKey, x as rollupValue, y as coerceNumeric } from "../promptSafety-Bp780ezk.js";
4
4
  import { n as projectRecordFields, t as mergeIntoExisting } from "../project-B0j_Vf_Q.js";
5
5
  //#region src/collection/core/presentCollection.ts
6
6
  var TOOL_NAME = "presentCollection";
@@ -634,6 +634,6 @@ var buildOntologyGraph = (entries) => {
634
634
  };
635
635
  };
636
636
  //#endregion
637
- export { AGENT_INGEST_KIND, COMPUTED_TYPES, ENUM_ALERT, ENUM_NEUTRAL, ENUM_NUDGE, FEED_SCHEDULES, INGEST_KINDS, MINUTES_PER_DAY, SAFE_RECORD_ID_PATTERN, SAFE_SLUG_PATTERN, TOOL_DEFINITION, TOOL_NAME, actionVisible, agentActionRunKey, assignLanes, backlinkRows, boolSortValue, bucketRecords, buildMonthGrid, buildOntologyGraph, buildUpdatedRecord, cellKey, chipMatches, coerceInlineValue, coerceNumeric, collectionCardKey, collectionKeyId, collectionKeyName, compareSortValues, compareYmd, completionCoveredByFieldChip, dateOf, dateSortValue, daySlice, defangForPrompt, deriveAll, derivedSortValue, draftToRecord, embedTargetId, emptyRow, enumColorClasses, enumSortValue, enumValueIndex, errorMessage, evaluateDerived, executePresentCollection, fieldDefaultValue, fieldText, fieldTextOrNull, fieldVisible, firstDateField, firstMissingRequiredField, firstUnknownDefault, flagFieldValue, generateUniqueId, isFieldDrivenEvery, isLocalCollectionKey, isReadOnlySchema, isSafeRecordId, isSafeSlug, isSharedCollectionKey, isSortableField, isValidCollectionName, itemIdOf, itemIsDone, itemLabelOf, itemMatchesQuery, labelFieldFor, localCollectionKeyOf, matchesWhere, mergeIntoExisting, monthAnchorDate, newItemId, nextSortDirection, nextUniqueItemId, numericSortValue, parseCollectionKeyId, parseIsoDate, parseIsoDateTime, parseTimeRange, projectBacklinkRow, projectRecordFields, recordSpan, resolveEnumColor, resolveIcon, resolveRowRefs, rollupValue, rowFromItem, rowIdOf, sameCollectionCard, sameCollectionKey, scalarSortValue, schemaDefaults, selectDynamicRecord, sharedCollectionKey, shortHexId, skillCommandSeed, snapshotEmptyEnums, sortItems, sortValueOf, spanCoversDay, storageKindFor, stringSortValue, toPresentCollectionData, toggleChecked, uniqueBacklinkSources, uniqueEmbedTargets, uniqueRefTargets, viaMatches, whenMatches, withCardScope, ymdKey };
637
+ export { AGENT_INGEST_KIND, COMPUTED_TYPES, ENUM_ALERT, ENUM_NEUTRAL, ENUM_NUDGE, FEED_SCHEDULES, INGEST_KINDS, MINUTES_PER_DAY, SAFE_RECORD_ID_PATTERN, SAFE_SLUG_PATTERN, TOOL_DEFINITION, TOOL_NAME, actionVisible, agentActionRunKey, assignLanes, backlinkRows, boolSortValue, bucketRecords, buildMonthGrid, buildOntologyGraph, buildUpdatedRecord, cellKey, chipMatches, coerceInlineValue, coerceNumeric, collectionCardKey, collectionKeyId, collectionKeyName, compareSortValues, compareYmd, completionCoveredByFieldChip, dateOf, dateSortValue, daySlice, decodeRecordTimes, decodeServerTime, defangForPrompt, deriveAll, derivedSortValue, draftToRecord, embedTargetId, emptyRow, encodeRecordTimes, encodeServerTime, enumColorClasses, enumSortValue, enumValueIndex, errorMessage, evaluateDerived, executePresentCollection, fieldDefaultValue, fieldText, fieldTextOrNull, fieldVisible, firstDateField, firstMissingRequiredField, firstUnknownDefault, flagFieldValue, generateUniqueId, isCanonicalServerTime, isFieldDrivenEvery, isLocalCollectionKey, isReadOnlySchema, isSafeRecordId, isSafeSlug, isSharedCollectionKey, isSortableField, isValidCollectionName, itemIdOf, itemIsDone, itemLabelOf, itemMatchesQuery, labelFieldFor, localCollectionKeyOf, matchesWhere, mergeIntoExisting, monthAnchorDate, newItemId, nextSortDirection, nextUniqueItemId, numericSortValue, parseCollectionKeyId, parseIsoDate, parseIsoDateTime, parseTimeRange, projectBacklinkRow, projectRecordFields, recordSpan, resolveEnumColor, resolveIcon, resolveRowRefs, rollupValue, rowFromItem, rowIdOf, sameCollectionCard, sameCollectionKey, scalarSortValue, schemaDefaults, selectDynamicRecord, serverTimeMillis, serverTimeOf, sharedCollectionKey, shortHexId, skillCommandSeed, snapshotEmptyEnums, sortItems, sortValueOf, spanCoversDay, storageKindFor, stringSortValue, toPresentCollectionData, toggleChecked, uniqueBacklinkSources, uniqueEmbedTargets, uniqueRefTargets, viaMatches, whenMatches, withCardScope, ymdKey };
638
638
 
639
639
  //# sourceMappingURL=index.js.map
@@ -4,9 +4,9 @@ const require_dist = require("../../../dist-CsgSfWwR.cjs");
4
4
  const require_root = require("../../../root-rPH6FGDT.cjs");
5
5
  const require_relPath = require("../../../relPath-CTAjGdCL.cjs");
6
6
  const require_utils_fetch = require("../../../utils/fetch.cjs");
7
- const require_discovery = require("../../../discovery-B9EESjHP.cjs");
7
+ const require_discovery = require("../../../discovery-Dl9nRa-m.cjs");
8
8
  const require_templatePath = require("../../../templatePath-27vUZowm.cjs");
9
- const require_server = require("../../../server-DSrRdPvf.cjs");
9
+ const require_server = require("../../../server-DZkN0GGh.cjs");
10
10
  const require_skill_bridge_index = require("../../../skill-bridge/index.cjs");
11
11
  const require_types = require("../../../types-CNqkLT4M.cjs");
12
12
  let node_fs = require("node:fs");
@@ -2,9 +2,9 @@ import { l as isUnknownArray, t as errorMessage } from "../../../dist-D8zokgGo.j
2
2
  import { n as writeFileAtomic } from "../../../root-BMroU_mB.js";
3
3
  import { n as toPosixRelPath } from "../../../relPath-DW8MC8VO.js";
4
4
  import { fetchWithTimeout } from "../../../utils/fetch.js";
5
- import { $ as collectionsRegistriesConfigPath, G as safeRecordId, ct as projectSkillsDir, mt as skillsStagingDir, nt as getWorkspaceRoot, ot as log, r as loadCollection, s as CollectionSchemaZ, t as acceptParsedSchema } from "../../../discovery-D88x2DmW.js";
5
+ import { $ as collectionsRegistriesConfigPath, G as safeRecordId, ct as projectSkillsDir, mt as skillsStagingDir, nt as getWorkspaceRoot, ot as log, r as loadCollection, s as CollectionSchemaZ, t as acceptParsedSchema } from "../../../discovery-P64fgtuD.js";
6
6
  import { t as isSafeActionTemplatePath } from "../../../templatePath-k_WNbL_Q.js";
7
- import { g as ONE_SECOND_MS } from "../../../server-dijB4BYv.js";
7
+ import { v as ONE_SECOND_MS } from "../../../server-C-Ngd3g2.js";
8
8
  import { claudeSkillDir, dataSkillDir, mirrorSkillWrite } from "../../../skill-bridge/index.js";
9
9
  import { n as parseRegistryIndex, r as isRecord, t as OFFICIAL_REGISTRY_NAME } from "../../../types-BKVZrtyc.js";
10
10
  import { readFileSync } from "node:fs";
@@ -1,9 +1,9 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_root = require("../../root-rPH6FGDT.cjs");
3
- const require_itemId = require("../../itemId-Dzrr3vs5.cjs");
4
- const require_discovery = require("../../discovery-B9EESjHP.cjs");
3
+ const require_itemId = require("../../itemId-CEQdK01z.cjs");
4
+ const require_discovery = require("../../discovery-Dl9nRa-m.cjs");
5
5
  const require_templatePath = require("../../templatePath-27vUZowm.cjs");
6
- const require_server = require("../../server-DSrRdPvf.cjs");
6
+ const require_server = require("../../server-DZkN0GGh.cjs");
7
7
  exports.APP_MANIFEST_FILE = require_discovery.APP_MANIFEST_FILE;
8
8
  exports.BackendUnavailableError = require_discovery.BackendUnavailableError;
9
9
  exports.COLLECTION_ROOT_REQUIRED = require_discovery.COLLECTION_ROOT_REQUIRED;
@@ -12,6 +12,8 @@ exports.CollectionQueryZ = require_discovery.CollectionQueryZ;
12
12
  exports.CollectionSchemaZ = require_discovery.CollectionSchemaZ;
13
13
  exports.DEFAULT_QUERY_ROWS = require_discovery.DEFAULT_QUERY_ROWS;
14
14
  exports.MAX_CSV_ROWS = require_discovery.MAX_CSV_ROWS;
15
+ exports.MAX_ITEMS_FILE_BYTES = require_server.MAX_ITEMS_FILE_BYTES;
16
+ exports.MAX_PUT_ITEMS = require_server.MAX_PUT_ITEMS;
15
17
  exports.MAX_QUERY_ROWS = require_discovery.MAX_QUERY_ROWS;
16
18
  exports.MAX_RECORD_ISSUES = require_server.MAX_RECORD_ISSUES;
17
19
  exports.MAX_SCHEMA_ISSUES = require_server.MAX_SCHEMA_ISSUES;
@@ -1,6 +1,6 @@
1
1
  import { t as canonicalRoot } from "../../root-BMroU_mB.js";
2
- import { N as COMPUTED_TYPES } from "../../itemId-DfTm08jm.js";
3
- import { A as readItem, B as SCHEMA_FILE, C as CollectionQueryZ, D as generateItemId, E as deleteItem, F as loadAppManifest, G as safeRecordId, H as itemFilePath, I as parseAppManifest, J as isBackendUnavailable, K as safeSlugName, L as sharedItemsPath, M as writeItem, N as APP_MANIFEST_FILE, O as isRegularFile, P as appManifestReason, Q as collectionChangePayload, R as pageFromFullRead, S as compileJsonlQuery, T as MAX_QUERY_ROWS, U as resolveDataDir, V as isContainedInRoot, W as resolveTemplatePath, Y as COLLECTION_ROOT_REQUIRED, Z as collectionChangeKey, _ as dedupeByRecordId, a as toDetail, at as localCollectionKey, d as storeFor, dt as setFirestoreAccessor, et as configureCollectionHost, ft as setSharedCollectionsSupport, g as decodeCsvRecordId, h as csvRowToItem, i as resolvePrimaryField, j as resolveCreateItemId, k as listItems, l as collectionWritable, lt as publishCollectionChange, n as discoverCollections, nt as getWorkspaceRoot, o as toSummary, ot as log, p as MAX_CSV_ROWS, pt as sharedCollectionChangePayload, q as BackendUnavailableError, r as loadCollection, rt as hostSupportsSharedCollections, s as CollectionSchemaZ, st as peekWorkspaceRoot, t as acceptParsedSchema, tt as firestoreHandle, u as readOnlyRefusal, ut as setCollectionChangePublisher, v as encodeCsvRecordId, w as DEFAULT_QUERY_ROWS, x as compileCsvQuery, y as normalizeCsvValue, z as projectItemFields } from "../../discovery-D88x2DmW.js";
2
+ import { B as COMPUTED_TYPES } from "../../itemId-BY2YPBkT.js";
3
+ import { A as readItem, B as SCHEMA_FILE, C as CollectionQueryZ, D as generateItemId, E as deleteItem, F as loadAppManifest, G as safeRecordId, H as itemFilePath, I as parseAppManifest, J as isBackendUnavailable, K as safeSlugName, L as sharedItemsPath, M as writeItem, N as APP_MANIFEST_FILE, O as isRegularFile, P as appManifestReason, Q as collectionChangePayload, R as pageFromFullRead, S as compileJsonlQuery, T as MAX_QUERY_ROWS, U as resolveDataDir, V as isContainedInRoot, W as resolveTemplatePath, Y as COLLECTION_ROOT_REQUIRED, Z as collectionChangeKey, _ as dedupeByRecordId, a as toDetail, at as localCollectionKey, d as storeFor, dt as setFirestoreAccessor, et as configureCollectionHost, ft as setSharedCollectionsSupport, g as decodeCsvRecordId, h as csvRowToItem, i as resolvePrimaryField, j as resolveCreateItemId, k as listItems, l as collectionWritable, lt as publishCollectionChange, n as discoverCollections, nt as getWorkspaceRoot, o as toSummary, ot as log, p as MAX_CSV_ROWS, pt as sharedCollectionChangePayload, q as BackendUnavailableError, r as loadCollection, rt as hostSupportsSharedCollections, s as CollectionSchemaZ, st as peekWorkspaceRoot, t as acceptParsedSchema, tt as firestoreHandle, u as readOnlyRefusal, ut as setCollectionChangePublisher, v as encodeCsvRecordId, w as DEFAULT_QUERY_ROWS, x as compileCsvQuery, y as normalizeCsvValue, z as projectItemFields } from "../../discovery-P64fgtuD.js";
4
4
  import { i as isSafeTemplatePath, n as isSafeCustomViewI18nPath, r as isSafeCustomViewPath, t as isSafeActionTemplatePath } from "../../templatePath-k_WNbL_Q.js";
5
- import { A as runQueryOverRows, C as STORE_UNREADABLE, D as recordFieldProblem, E as compileRecordZ, F as readCustomViewI18n, I as readSkillTemplate, M as buildCollectionActionSeedPrompt, N as promptPathsFor, O as runCollectionQuery, P as readCustomViewHtml, S as MAX_RECORD_ISSUES, T as validateRecordObject, _ as computeCollectionIcon, a as deleteCollection, b as applyMutateAction, c as computeSuccessor, d as isTriggerDue, f as maybeSpawnSuccessor, h as successorId, i as deleteCustomView, j as buildActionSeedPrompt, k as enrichItems, l as daysInMonth, m as resolveEvery, n as MAX_UNSELECTIVE_ITEMS, o as deleteCollectionRefusalMessage, p as parseCivil, r as makeManageCollectionTool, s as advanceTriggerDate, t as MAX_SCHEMA_ISSUES, u as formatCivil, v as buildWorkspaceOntology, w as validateCollectionRecords, x as firstMutateParamProblem, y as schemaRelations } from "../../server-dijB4BYv.js";
6
- export { APP_MANIFEST_FILE, BackendUnavailableError, COLLECTION_ROOT_REQUIRED, COMPUTED_TYPES, CollectionQueryZ, CollectionSchemaZ, DEFAULT_QUERY_ROWS, MAX_CSV_ROWS, MAX_QUERY_ROWS, MAX_RECORD_ISSUES, MAX_SCHEMA_ISSUES, MAX_UNSELECTIVE_ITEMS, SCHEMA_FILE, STORE_UNREADABLE, acceptParsedSchema, advanceTriggerDate, appManifestReason, applyMutateAction, buildActionSeedPrompt, buildCollectionActionSeedPrompt, buildWorkspaceOntology, canonicalRoot, collectionChangeKey, collectionChangePayload, collectionWritable, compileCsvQuery, compileJsonlQuery, compileRecordZ, computeCollectionIcon, computeSuccessor, configureCollectionHost, csvRowToItem, daysInMonth, decodeCsvRecordId, dedupeByRecordId, deleteCollection, deleteCollectionRefusalMessage, deleteCustomView, deleteItem, discoverCollections, encodeCsvRecordId, enrichItems, firestoreHandle, firstMutateParamProblem, formatCivil, generateItemId, getWorkspaceRoot, hostSupportsSharedCollections, isBackendUnavailable, isContainedInRoot, isRegularFile, isSafeActionTemplatePath, isSafeCustomViewI18nPath, isSafeCustomViewPath, isSafeTemplatePath, isTriggerDue, itemFilePath, listItems, loadAppManifest, loadCollection, localCollectionKey, log, makeManageCollectionTool, maybeSpawnSuccessor, normalizeCsvValue, pageFromFullRead, parseAppManifest, parseCivil, peekWorkspaceRoot, projectItemFields, promptPathsFor, publishCollectionChange, readCustomViewHtml, readCustomViewI18n, readItem, readOnlyRefusal, readSkillTemplate, recordFieldProblem, resolveCreateItemId, resolveDataDir, resolveEvery, resolvePrimaryField, resolveTemplatePath, runCollectionQuery, runQueryOverRows, safeRecordId, safeSlugName, schemaRelations, setCollectionChangePublisher, setFirestoreAccessor, setSharedCollectionsSupport, sharedCollectionChangePayload, sharedItemsPath, storeFor, successorId, toDetail, toSummary, validateCollectionRecords, validateRecordObject, writeItem };
5
+ import { A as runCollectionQuery, C as firstMutateParamProblem, D as validateRecordObject, E as validateCollectionRecords, F as promptPathsFor, I as readCustomViewHtml, L as readCustomViewI18n, M as runQueryOverRows, N as buildActionSeedPrompt, O as compileRecordZ, P as buildCollectionActionSeedPrompt, R as readSkillTemplate, S as applyMutateAction, T as STORE_UNREADABLE, _ as successorId, a as makeManageCollectionTool, b as buildWorkspaceOntology, c as deleteCollectionRefusalMessage, d as daysInMonth, f as formatCivil, g as resolveEvery, h as parseCivil, i as MAX_UNSELECTIVE_ITEMS, j as enrichItems, k as recordFieldProblem, l as advanceTriggerDate, m as maybeSpawnSuccessor, n as MAX_PUT_ITEMS, o as deleteCustomView, p as isTriggerDue, r as MAX_SCHEMA_ISSUES, s as deleteCollection, t as MAX_ITEMS_FILE_BYTES, u as computeSuccessor, w as MAX_RECORD_ISSUES, x as schemaRelations, y as computeCollectionIcon } from "../../server-C-Ngd3g2.js";
6
+ export { APP_MANIFEST_FILE, BackendUnavailableError, COLLECTION_ROOT_REQUIRED, COMPUTED_TYPES, CollectionQueryZ, CollectionSchemaZ, DEFAULT_QUERY_ROWS, MAX_CSV_ROWS, MAX_ITEMS_FILE_BYTES, MAX_PUT_ITEMS, MAX_QUERY_ROWS, MAX_RECORD_ISSUES, MAX_SCHEMA_ISSUES, MAX_UNSELECTIVE_ITEMS, SCHEMA_FILE, STORE_UNREADABLE, acceptParsedSchema, advanceTriggerDate, appManifestReason, applyMutateAction, buildActionSeedPrompt, buildCollectionActionSeedPrompt, buildWorkspaceOntology, canonicalRoot, collectionChangeKey, collectionChangePayload, collectionWritable, compileCsvQuery, compileJsonlQuery, compileRecordZ, computeCollectionIcon, computeSuccessor, configureCollectionHost, csvRowToItem, daysInMonth, decodeCsvRecordId, dedupeByRecordId, deleteCollection, deleteCollectionRefusalMessage, deleteCustomView, deleteItem, discoverCollections, encodeCsvRecordId, enrichItems, firestoreHandle, firstMutateParamProblem, formatCivil, generateItemId, getWorkspaceRoot, hostSupportsSharedCollections, isBackendUnavailable, isContainedInRoot, isRegularFile, isSafeActionTemplatePath, isSafeCustomViewI18nPath, isSafeCustomViewPath, isSafeTemplatePath, isTriggerDue, itemFilePath, listItems, loadAppManifest, loadCollection, localCollectionKey, log, makeManageCollectionTool, maybeSpawnSuccessor, normalizeCsvValue, pageFromFullRead, parseAppManifest, parseCivil, peekWorkspaceRoot, projectItemFields, promptPathsFor, publishCollectionChange, readCustomViewHtml, readCustomViewI18n, readItem, readOnlyRefusal, readSkillTemplate, recordFieldProblem, resolveCreateItemId, resolveDataDir, resolveEvery, resolvePrimaryField, resolveTemplatePath, runCollectionQuery, runQueryOverRows, safeRecordId, safeSlugName, schemaRelations, setCollectionChangePublisher, setFirestoreAccessor, setSharedCollectionsSupport, sharedCollectionChangePayload, sharedItemsPath, storeFor, successorId, toDetail, toSummary, validateCollectionRecords, validateRecordObject, writeItem };
@@ -5,6 +5,17 @@ import { DiscoveryOptions } from './discovery';
5
5
  export declare const MAX_UNSELECTIVE_ITEMS = 200;
6
6
  /** Cap the rejected-schema issue list so a deeply-broken schema can't flood the result. */
7
7
  export declare const MAX_SCHEMA_ISSUES = 20;
8
+ /** Cap the rows one putItems call may write. `putOneItem` validates and
9
+ * writes one record at a time, so a large `itemsFile` holds the tool call
10
+ * open for minutes. Over the cap the call is refused WHOLE — a truncating
11
+ * write that reported success would leave a half-filled collection nobody
12
+ * knows is half-filled. */
13
+ export declare const MAX_PUT_ITEMS = 1000;
14
+ /** Refuse an `itemsFile` larger than this, from `stat` and before any read.
15
+ * The row cap alone cannot bound the work: the file has to be read and parsed
16
+ * WHOLE before there are rows to count, so a huge blob is paid for in full
17
+ * first. 8 MiB is far past what 1000 records need and far short of trouble. */
18
+ export declare const MAX_ITEMS_FILE_BYTES: number;
8
19
  /** Workspace-targeting overrides, threaded to every collections call.
9
20
  * Production: `{}` (the configured collection host's workspace).
10
21
  * Tests: a tmpdir + empty user skills dir. `refreshAfterWrite` is the
@@ -44,6 +55,20 @@ export type ManageCollectionDeps = DiscoveryOptions & {
44
55
  * returning null, because the doc variant is a statement the host makes, not
45
56
  * something the package should infer. */
46
57
  stagedSkillAuthoring?: boolean | undefined;
58
+ /** Where the workspace is mounted INSIDE this host's agent sandbox
59
+ * (MulmoClaude's `CONTAINER_WORKSPACE_PATH`, `/home/node/mulmoclaude`).
60
+ *
61
+ * Only `putItems`' `itemsFile` needs it, and it needs it badly: a sandboxed
62
+ * agent's absolute paths are CONTAINER paths, while this tool body runs on
63
+ * the host. Read verbatim, `/home/node/mulmoclaude/rows.json` is ENOENT on
64
+ * every host whose workspace is not literally that directory — i.e. all of
65
+ * them. Given this, the prefix is translated back to the workspace root, so
66
+ * the path the agent wrote to and the file the host reads are the same bytes.
67
+ *
68
+ * Omitted (a host with no sandbox, or one that mounts the workspace at its
69
+ * real path), absolute paths are used as given. Binding it is harmless when
70
+ * the sandbox is off — the prefix simply never appears. */
71
+ sandboxWorkspacePath?: string | undefined;
47
72
  };
48
73
  export declare function makeManageCollectionTool(deps?: ManageCollectionDeps): {
49
74
  definition: {
@@ -82,6 +107,10 @@ export declare function makeManageCollectionTool(deps?: ManageCollectionDeps): {
82
107
  };
83
108
  description: string;
84
109
  };
110
+ itemsFile: {
111
+ type: string;
112
+ description: string;
113
+ };
85
114
  mode: {
86
115
  type: string;
87
116
  enum: string[];
@@ -1,10 +1,10 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_root = require("../root-rPH6FGDT.cjs");
3
- const require_itemId = require("../itemId-Dzrr3vs5.cjs");
4
- const require_promptSafety = require("../promptSafety-CuczAN9d.cjs");
3
+ const require_itemId = require("../itemId-CEQdK01z.cjs");
4
+ const require_promptSafety = require("../promptSafety-C0BWDsfT.cjs");
5
5
  require("../collection/index.cjs");
6
- const require_discovery = require("../discovery-B9EESjHP.cjs");
7
- const require_server = require("../server-DSrRdPvf.cjs");
6
+ const require_discovery = require("../discovery-Dl9nRa-m.cjs");
7
+ const require_server = require("../server-DZkN0GGh.cjs");
8
8
  const require_notifier = require("../notifier-DZJj0sOh.cjs");
9
9
  let node_fs_promises = require("node:fs/promises");
10
10
  //#region src/collection-watchers/config.ts
@@ -1,9 +1,9 @@
1
1
  import { t as canonicalRoot } from "../root-BMroU_mB.js";
2
- import { B as fieldText, x as isValidCollectionName } from "../itemId-DfTm08jm.js";
3
- import { E as whenMatches, f as itemIsDone } from "../promptSafety-CJ-sC1pX.js";
2
+ import { O as isValidCollectionName, q as fieldText } from "../itemId-BY2YPBkT.js";
3
+ import { E as whenMatches, f as itemIsDone } from "../promptSafety-Bp780ezk.js";
4
4
  import "../collection/index.js";
5
- import { H as itemFilePath, Q as collectionChangePayload, d as storeFor, lt as publishCollectionChange, n as discoverCollections, pt as sharedCollectionChangePayload, r as loadCollection, st as peekWorkspaceRoot, tt as firestoreHandle } from "../discovery-D88x2DmW.js";
6
- import { d as isTriggerDue, f as maybeSpawnSuccessor } from "../server-dijB4BYv.js";
5
+ import { H as itemFilePath, Q as collectionChangePayload, d as storeFor, lt as publishCollectionChange, n as discoverCollections, pt as sharedCollectionChangePayload, r as loadCollection, st as peekWorkspaceRoot, tt as firestoreHandle } from "../discovery-P64fgtuD.js";
6
+ import { m as maybeSpawnSuccessor, p as isTriggerDue } from "../server-C-Ngd3g2.js";
7
7
  import { d as publish, m as updateForPlugin, n as clear, s as listAll } from "../notifier-DhnwR82U.js";
8
8
  import { access } from "node:fs/promises";
9
9
  //#region src/collection-watchers/config.ts
@@ -1,7 +1,7 @@
1
1
  const require_rolldown_runtime = require("./rolldown-runtime-VH7oDXx4.cjs");
2
2
  const require_dist = require("./dist-CsgSfWwR.cjs");
3
3
  const require_root = require("./root-rPH6FGDT.cjs");
4
- const require_itemId = require("./itemId-Dzrr3vs5.cjs");
4
+ const require_itemId = require("./itemId-CEQdK01z.cjs");
5
5
  const require_project = require("./project-C1ep9pvo.cjs");
6
6
  const require_listen = require("./listen-UZA68Upt.cjs");
7
7
  const require_templatePath = require("./templatePath-27vUZowm.cjs");
@@ -10,6 +10,7 @@ let node_path = require("node:path");
10
10
  node_path = require_rolldown_runtime.__toESM(node_path, 1);
11
11
  let node_crypto = require("node:crypto");
12
12
  let node_fs_promises = require("node:fs/promises");
13
+ let firebase_firestore = require("firebase/firestore");
13
14
  let node_os = require("node:os");
14
15
  let iconv_lite = require("iconv-lite");
15
16
  iconv_lite = require_rolldown_runtime.__toESM(iconv_lite, 1);
@@ -476,10 +477,10 @@ async function guarded(key, email, run) {
476
477
  * It also removes the reason a submit path would have had to accept the
477
478
  * primary key as a `createField` at all — a submission that cannot name its
478
479
  * own id is exactly right when the id is the thing being assigned. */
479
- function toItem(data, docId, primaryKey) {
480
+ function toItem(data, docId, schema) {
480
481
  return require_dist.isRecord(data) ? {
481
- ...data,
482
- [primaryKey]: docId
482
+ ...require_itemId.decodeRecordTimes(data, schema),
483
+ [schema.primaryKey]: docId
483
484
  } : null;
484
485
  }
485
486
  /** Record ids are validated with the SAME helper every other backend uses.
@@ -491,26 +492,26 @@ function withSafeId(itemId, onInvalid, run) {
491
492
  if (safeId === null) return onInvalid();
492
493
  return run(safeId, requireHandle());
493
494
  }
494
- async function firestoreList(key, primaryKey) {
495
+ async function firestoreList(key, schema) {
495
496
  const { docs, email } = requireHandle();
496
- return (await guarded(key, email, () => docs.list(sharedItemsPath(key)))).map((entry) => toItem(entry.data, entry.id, primaryKey)).filter((item) => item !== null);
497
+ return (await guarded(key, email, () => docs.list(sharedItemsPath(key)))).map((entry) => toItem(entry.data, entry.id, schema)).filter((item) => item !== null);
497
498
  }
498
499
  /** Paging is emulated over a full ordered read rather than pushed into
499
500
  * Firestore: `offset` has no server-side form there (the cursor API needs the
500
501
  * preceding document, which a stateless offset/limit call doesn't have), and
501
502
  * `total` needs the full count anyway. Hence `nativePaging: false` — the
502
503
  * capability is honest about the cost. */
503
- async function firestorePage(key, primaryKey, opts) {
504
- const items = await firestoreList(key, primaryKey);
504
+ async function firestorePage(key, schema, opts) {
505
+ const items = await firestoreList(key, schema);
505
506
  const offset = Math.max(0, opts.offset ?? 0);
506
507
  return {
507
- items: projectItemFields(opts.limit === void 0 ? items.slice(offset) : items.slice(offset, offset + Math.max(0, opts.limit)), opts.fields, primaryKey),
508
+ items: projectItemFields(opts.limit === void 0 ? items.slice(offset) : items.slice(offset, offset + Math.max(0, opts.limit)), opts.fields, schema.primaryKey),
508
509
  total: items.length,
509
510
  truncated: false
510
511
  };
511
512
  }
512
- async function firestoreRead(key, itemId, primaryKey) {
513
- return withSafeId(itemId, () => Promise.resolve(null), async (safeId, { docs, email }) => toItem(await guarded(key, email, () => docs.get(sharedItemsPath(key), safeId)), safeId, primaryKey));
513
+ async function firestoreRead(key, itemId, schema) {
514
+ return withSafeId(itemId, () => Promise.resolve(null), async (safeId, { docs, email }) => toItem(await guarded(key, email, () => docs.get(sharedItemsPath(key), safeId)), safeId, schema));
514
515
  }
515
516
  /** Publish the "records changed" ping for a shared collection.
516
517
  *
@@ -525,18 +526,35 @@ function publishShared(key, ids, operation) {
525
526
  op: operation
526
527
  }, key.aid));
527
528
  }
528
- async function firestoreWrite(key, itemId, item, opts) {
529
+ /** The write half of the decode above, and it is not optional.
530
+ *
531
+ * A record is read, edited and written back WHOLE — `putItems`' upsert
532
+ * replaces the document — so a decoded stamp would return to Firestore as a
533
+ * string. The rules freeze that field by comparing `diff().affectedKeys()`, so
534
+ * that write is refused and so is every later one: the record becomes
535
+ * permanently unupdatable. Dropping the key instead does not help, because a
536
+ * removed key is an affected key too.
537
+ *
538
+ * Only a `datetime` field carrying the exact canonical form is converted, and
539
+ * that form is only ever produced by the decode — so the pair is closed over
540
+ * its own output and cannot re-type a value somebody else wrote. */
541
+ async function firestoreWrite(key, itemId, item, schema, opts) {
529
542
  return withSafeId(itemId, () => Promise.resolve({
530
543
  kind: "invalid-id",
531
544
  itemId
532
545
  }), async (safeId, { docs, email }) => {
533
546
  const collectionPath = sharedItemsPath(key);
547
+ const asTimestamp = (parts) => new firebase_firestore.Timestamp(parts.seconds, parts.nanoseconds);
534
548
  if (opts.refuseOverwrite) {
535
- if (!await guarded(key, email, () => docs.create(collectionPath, safeId, item))) return {
549
+ if (!await guarded(key, email, () => docs.create(collectionPath, safeId, require_itemId.encodeRecordTimes(item, null, asTimestamp)))) return {
536
550
  kind: "conflict",
537
551
  itemId: safeId
538
552
  };
539
- } else await guarded(key, email, () => docs.set(collectionPath, safeId, item));
553
+ } else {
554
+ const previous = await guarded(key, email, () => docs.get(collectionPath, safeId));
555
+ const stored = require_itemId.encodeRecordTimes(item, require_dist.isRecord(previous) ? previous : null, asTimestamp);
556
+ await guarded(key, email, () => docs.set(collectionPath, safeId, stored));
557
+ }
540
558
  if (opts.slug) publishShared(key, [safeId], "upsert");
541
559
  return {
542
560
  kind: "ok",
@@ -669,7 +687,7 @@ function armSharedWatch(key, onChange) {
669
687
  /** The store factory registered for `storage.type === "firestore"`.
670
688
  * Synchronous and connection-agnostic by contract — see the header. */
671
689
  function firestoreStoreFor(collection, opts) {
672
- const { primaryKey } = collection.schema;
690
+ const { schema } = collection;
673
691
  const ioOpts = {
674
692
  ...opts,
675
693
  slug: opts.slug ?? collection.slug
@@ -680,10 +698,10 @@ function firestoreStoreFor(collection, opts) {
680
698
  nativeQuery: false,
681
699
  nativePaging: false
682
700
  },
683
- list: async () => firestoreList(keyOf(collection), primaryKey),
684
- page: async (pageOpts = {}) => firestorePage(keyOf(collection), primaryKey, pageOpts),
685
- read: async (itemId) => firestoreRead(keyOf(collection), itemId, primaryKey),
686
- write: async (itemId, item, writeOpts = {}) => firestoreWrite(keyOf(collection), itemId, item, {
701
+ list: async () => firestoreList(keyOf(collection), schema),
702
+ page: async (pageOpts = {}) => firestorePage(keyOf(collection), schema, pageOpts),
703
+ read: async (itemId) => firestoreRead(keyOf(collection), itemId, schema),
704
+ write: async (itemId, item, writeOpts = {}) => firestoreWrite(keyOf(collection), itemId, item, schema, {
687
705
  ...ioOpts,
688
706
  refuseOverwrite: writeOpts.refuseOverwrite
689
707
  }),
@@ -3743,4 +3761,4 @@ Object.defineProperty(exports, "writeItem", {
3743
3761
  }
3744
3762
  });
3745
3763
 
3746
- //# sourceMappingURL=discovery-B9EESjHP.cjs.map
3764
+ //# sourceMappingURL=discovery-Dl9nRa-m.cjs.map