@mulmoclaude/core 4.2.0 → 4.3.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.
@@ -193,12 +193,23 @@ skipped, never crashes the host):
193
193
 
194
194
  Every field spec needs a `type` and a `label`. Extra keys by type:
195
195
 
196
- - **`datetime`** — no extra keys. Stored as a `YYYY-MM-DDTHH:MM` string and
197
- edited with a native date+time picker. Use it (as `calendarField` /
198
- `calendarEndField`) when an event has a real start/end clock — the calendar's
199
- day view then draws each record as a proportional time block. For the common
200
- "date column + separate time column" shape, keep `date` and point
201
- `calendarTimeField` at the time string instead.
196
+ - **`datetime`** — no extra keys. Stored as a `YYYY-MM-DDTHH:MM` string (seconds
197
+ optional) and edited with a native date+time picker. It is a **local wall
198
+ clock, not an instant**: no `Z`, no `+09:00` offset. `08:00` means eight in the
199
+ morning wherever the records are read, which is what a schedule means and what
200
+ the calendar can place. So a generated value must be FORMATTED, never
201
+ converted — `new Date(...).toISOString()` is wrong twice over: it appends the
202
+ `Z` (`putItems` writes the row but reports it in `lint`, a full `getItems`
203
+ listing warns about it, and a shared app's publish refuses it outright), and
204
+ it shifts the hours by the generating machine's offset, so a Tokyo 08:00 written on a US laptop reads back as the afternoon.
205
+ Build the string from the parts you already have (`${date}T${hh}:${mm}`). The
206
+ one `Z`-suffixed datetime that is legal is a shared app's server-stamped
207
+ field — nine fractional digits, written by the SERVER, never by you. Anything
208
+ else ending in `Z` (including `toISOString()`'s three digits) is linted.
209
+ Use it (as `calendarField` / `calendarEndField`) when an event has a real
210
+ start/end clock — the calendar's day view then draws each record as a
211
+ proportional time block. For the common "date column + separate time column"
212
+ shape, keep `date` and point `calendarTimeField` at the time string instead.
202
213
  - **`enum`** — `values: ["draft", "sent", "paid"]` (non-empty strings). Renders
203
214
  a `<select>`; stored as a plain string. Optional `default: "draft"` pre-fills
204
215
  a NEW record — the Add form opens on it, and a `putItems` row in `create` mode
@@ -1071,6 +1071,59 @@ Reading the refusal you got:
1071
1071
  Do NOT spawn the MCP bridge yourself. A hand-written JSON-RPC client fails
1072
1072
  invisibly and can leave a partially written collection that nothing reports.
1073
1073
 
1074
+ ## `putItems` came back with a `lint` block — the rows were written, the values are the wrong shape
1075
+
1076
+ ### Symptoms
1077
+
1078
+ - A `putItems` result carries `lint` beside `written`: `{ total, note, rows: [{ id, problem }] }`.
1079
+ - The problem text reads like a type complaint rather than a missing field —
1080
+ `not a YYYY-MM-DDTHH:MM datetime`, `is not numeric`, `is not a real YYYY-MM-DD date`,
1081
+ `is not a boolean`.
1082
+ - `rejected` is empty, and the records are on disk, so nothing looks broken yet.
1083
+
1084
+ ### Cause
1085
+
1086
+ The write gate refuses only what makes a record unopenable: required fields,
1087
+ enum values, `primaryKey` = record id. The SHAPE of a value is checked by a
1088
+ second, report-only tier — refusing there would make a collection whose older
1089
+ rows predate the typed rules unwritable. So the row lands, and `lint` is the
1090
+ report.
1091
+
1092
+ It is not cosmetic. Every later reader runs the strict tier: a full `getItems`
1093
+ listing (and a selective one that missed an id) returns the same finding as a
1094
+ `warning` beside the rows, and publishing a shared app REFUSES these rows
1095
+ outright. A batch generated by a script is wrong in every row the same way, so
1096
+ this is the moment it costs the least to fix.
1097
+
1098
+ ### Fix
1099
+
1100
+ Correct the generator, then rewrite the rows (`mode: "upsert"`, same ids) and
1101
+ check that `lint` is gone. `lint.total` is the real count; `lint.rows` shows the
1102
+ first ten.
1103
+
1104
+ The one that recurs is `datetime`. It stores a **local wall clock** —
1105
+ `YYYY-MM-DDTHH:MM`, seconds optional, **no `Z` and no offset** — because `08:00`
1106
+ in a schedule means eight in the morning wherever it is read, and that is what
1107
+ the calendar can place. So format the string, never convert it:
1108
+
1109
+ ```js
1110
+ // WRONG — appends `Z` and shifts the hours by the generating machine's offset
1111
+ { startAt: new Date(`${day}T08:00`).toISOString() } // "2026-08-17T15:00:00.000Z"
1112
+
1113
+ // RIGHT — the clock you meant, written as the clock
1114
+ { startAt: `${day}T08:00` } // "2026-08-17T08:00"
1115
+ ```
1116
+
1117
+ The conversion is the more expensive half: had the format passed, a Tokyo court's
1118
+ 08:00 seeded on a US laptop would have published every slot seven hours out
1119
+ (eight, on the dates the other side of a DST change) with nothing to point at.
1120
+ The one `Z`-suffixed datetime the strict tier accepts is a shared app's
1121
+ server-stamped instant — nine fractional digits
1122
+ (`2026-08-15T01:45:54.605987654Z`), written by the server, not by you.
1123
+ `toISOString()`'s three digits are not that shape and are linted.
1124
+
1125
+ Field-by-field stored forms: `schemaDocs` with `topic: "Field types"`.
1126
+
1074
1127
  ## A tool your own instructions describe returns "No such tool available"
1075
1128
 
1076
1129
  ### Symptoms
@@ -6,7 +6,7 @@ const require_relPath = require("../../../relPath-CTAjGdCL.cjs");
6
6
  const require_utils_fetch = require("../../../utils/fetch.cjs");
7
7
  const require_discovery = require("../../../discovery-Dl9nRa-m.cjs");
8
8
  const require_templatePath = require("../../../templatePath-27vUZowm.cjs");
9
- const require_server = require("../../../server-DZkN0GGh.cjs");
9
+ const require_server = require("../../../server-CF1qllGV.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");
@@ -4,7 +4,7 @@ import { n as toPosixRelPath } from "../../../relPath-DW8MC8VO.js";
4
4
  import { fetchWithTimeout } from "../../../utils/fetch.js";
5
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 { v as ONE_SECOND_MS } from "../../../server-C-Ngd3g2.js";
7
+ import { y as ONE_SECOND_MS } from "../../../server-BvXO49Vj.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";
@@ -3,7 +3,7 @@ const require_root = require("../../root-rPH6FGDT.cjs");
3
3
  const require_itemId = require("../../itemId-CEQdK01z.cjs");
4
4
  const require_discovery = require("../../discovery-Dl9nRa-m.cjs");
5
5
  const require_templatePath = require("../../templatePath-27vUZowm.cjs");
6
- const require_server = require("../../server-DZkN0GGh.cjs");
6
+ const require_server = require("../../server-CF1qllGV.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;
@@ -14,6 +14,7 @@ exports.DEFAULT_QUERY_ROWS = require_discovery.DEFAULT_QUERY_ROWS;
14
14
  exports.MAX_CSV_ROWS = require_discovery.MAX_CSV_ROWS;
15
15
  exports.MAX_ITEMS_FILE_BYTES = require_server.MAX_ITEMS_FILE_BYTES;
16
16
  exports.MAX_PUT_ITEMS = require_server.MAX_PUT_ITEMS;
17
+ exports.MAX_PUT_LINT = require_server.MAX_PUT_LINT;
17
18
  exports.MAX_QUERY_ROWS = require_discovery.MAX_QUERY_ROWS;
18
19
  exports.MAX_RECORD_ISSUES = require_server.MAX_RECORD_ISSUES;
19
20
  exports.MAX_SCHEMA_ISSUES = require_server.MAX_SCHEMA_ISSUES;
@@ -2,5 +2,5 @@ import { t as canonicalRoot } from "../../root-BMroU_mB.js";
2
2
  import { B as COMPUTED_TYPES } from "../../itemId-BY2YPBkT.js";
3
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 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
+ import { A as recordFieldProblem, C as applyMutateAction, D as validateCollectionRecords, E as STORE_UNREADABLE, F as buildCollectionActionSeedPrompt, I as promptPathsFor, L as readCustomViewHtml, M as enrichItems, N as runQueryOverRows, O as validateRecordObject, P as buildActionSeedPrompt, R as readCustomViewI18n, S as schemaRelations, T as MAX_RECORD_ISSUES, _ as resolveEvery, a as MAX_UNSELECTIVE_ITEMS, b as computeCollectionIcon, c as deleteCollection, d as computeSuccessor, f as daysInMonth, g as parseCivil, h as maybeSpawnSuccessor, i as MAX_SCHEMA_ISSUES, j as runCollectionQuery, k as compileRecordZ, l as deleteCollectionRefusalMessage, m as isTriggerDue, n as MAX_PUT_ITEMS, o as makeManageCollectionTool, p as formatCivil, r as MAX_PUT_LINT, s as deleteCustomView, t as MAX_ITEMS_FILE_BYTES, u as advanceTriggerDate, v as successorId, w as firstMutateParamProblem, x as buildWorkspaceOntology, z as readSkillTemplate } from "../../server-BvXO49Vj.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_PUT_LINT, 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 };
@@ -11,6 +11,11 @@ export declare const MAX_SCHEMA_ISSUES = 20;
11
11
  * write that reported success would leave a half-filled collection nobody
12
12
  * knows is half-filled. */
13
13
  export declare const MAX_PUT_ITEMS = 1000;
14
+ /** Cap the strict-tier findings one putItems call reports back. The rows were
15
+ * WRITTEN, so this is a report and not a refusal — and a generated batch is
16
+ * wrong the same way in every row, which means ten samples plus the total say
17
+ * everything a thousand would. */
18
+ export declare const MAX_PUT_LINT = 10;
14
19
  /** Refuse an `itemsFile` larger than this, from `stat` and before any read.
15
20
  * The row cap alone cannot bound the work: the file has to be read and parsed
16
21
  * WHOLE before there are rows to count, so a huge blob is paid for in full
@@ -70,6 +75,25 @@ export type ManageCollectionDeps = DiscoveryOptions & {
70
75
  * the sandbox is off — the prefix simply never appears. */
71
76
  sandboxWorkspacePath?: string | undefined;
72
77
  };
78
+ /** One row the call did not take, or took and flagged: the id it was about and
79
+ * what to fix. Exported alongside `PutItemsLint`, whose `rows` are these. */
80
+ export interface RejectedRow {
81
+ id: string;
82
+ problem: string;
83
+ }
84
+ /** The `lint` block of a putItems result — the strict-tier findings on rows the
85
+ * call WROTE. Exported because it is part of the tool's answer, not an
86
+ * internal: a caller (or a test) that re-declares the shape inline is a copy
87
+ * that stops matching the day the block gains a field. */
88
+ export interface PutItemsLint {
89
+ /** Every flagged row, not just the shown ones — a capped `rows` must never be
90
+ * readable as a total. */
91
+ total: number;
92
+ /** What the reader has to know to act on findings sitting beside `written`. */
93
+ note: string;
94
+ /** The first `MAX_PUT_LINT` findings. */
95
+ rows: RejectedRow[];
96
+ }
73
97
  export declare function makeManageCollectionTool(deps?: ManageCollectionDeps): {
74
98
  definition: {
75
99
  name: string;
@@ -4,7 +4,7 @@ const require_itemId = require("../itemId-CEQdK01z.cjs");
4
4
  const require_promptSafety = require("../promptSafety-C0BWDsfT.cjs");
5
5
  require("../collection/index.cjs");
6
6
  const require_discovery = require("../discovery-Dl9nRa-m.cjs");
7
- const require_server = require("../server-DZkN0GGh.cjs");
7
+ const require_server = require("../server-CF1qllGV.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
@@ -3,7 +3,7 @@ import { O as isValidCollectionName, q as fieldText } from "../itemId-BY2YPBkT.j
3
3
  import { E as whenMatches, f as itemIsDone } from "../promptSafety-Bp780ezk.js";
4
4
  import "../collection/index.js";
5
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";
6
+ import { h as maybeSpawnSuccessor, m as isTriggerDue } from "../server-BvXO49Vj.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
@@ -5,7 +5,7 @@ const require_root = require("../../root-rPH6FGDT.cjs");
5
5
  const require_itemId = require("../../itemId-CEQdK01z.cjs");
6
6
  const require_project = require("../../project-C1ep9pvo.cjs");
7
7
  const require_discovery = require("../../discovery-Dl9nRa-m.cjs");
8
- const require_server = require("../../server-DZkN0GGh.cjs");
8
+ const require_server = require("../../server-CF1qllGV.cjs");
9
9
  const require_feeds_paths = require("../paths.cjs");
10
10
  const require_ingestTypes = require("../../ingestTypes-CaX7_s-K.cjs");
11
11
  const require_notifier = require("../../notifier-DZJj0sOh.cjs");
@@ -3,7 +3,7 @@ import { t as canonicalRoot } from "../../root-BMroU_mB.js";
3
3
  import { H as INGEST_KINDS, V as FEED_SCHEDULES } from "../../itemId-BY2YPBkT.js";
4
4
  import { t as mergeIntoExisting } from "../../project-B0j_Vf_Q.js";
5
5
  import { K as safeSlugName, U as resolveDataDir, _t as createHostSlot, d as storeFor, gt as createForwardingLogger, k as listItems, n as discoverCollections } from "../../discovery-P64fgtuD.js";
6
- import { F as promptPathsFor, P as buildCollectionActionSeedPrompt, R as readSkillTemplate } from "../../server-C-Ngd3g2.js";
6
+ import { F as buildCollectionActionSeedPrompt, I as promptPathsFor, z as readSkillTemplate } from "../../server-BvXO49Vj.js";
7
7
  import { FEEDS_DIR, feedDir, feedStatePath, feedsRoot, ingestStateDir, ingestStatePath } from "../paths.js";
8
8
  import { n as DEFAULT_FEED_MAX_ITEMS, r as isFeedSchedule, t as AGENT_INGEST_KIND } from "../../ingestTypes-CIy1lHjf.js";
9
9
  import { d as publish, n as clear } from "../../notifier-DhnwR82U.js";
@@ -1566,6 +1566,22 @@ var MAX_SCHEMA_ISSUES = 20;
1566
1566
  * write that reported success would leave a half-filled collection nobody
1567
1567
  * knows is half-filled. */
1568
1568
  var MAX_PUT_ITEMS = 1e3;
1569
+ /** Cap the strict-tier findings one putItems call reports back. The rows were
1570
+ * WRITTEN, so this is a report and not a refusal — and a generated batch is
1571
+ * wrong the same way in every row, which means ten samples plus the total say
1572
+ * everything a thousand would. */
1573
+ var MAX_PUT_LINT = 10;
1574
+ /** What the `lint` block SAYS, once per call. A bare list of findings on rows the
1575
+ * call also reports as `written` reads as noise; the reason to act is that the
1576
+ * next reader of these rows is stricter than the write was.
1577
+ *
1578
+ * Like everything else the tool says about validation, this describes the tool
1579
+ * with its gate ON. Under `ablateValidation` nothing is checked — `rejected`
1580
+ * stays empty too — and the caveat is deliberately NOT written into the
1581
+ * agent-facing text: ablation exists to measure what a model does without the
1582
+ * guardrails, and a model told "unless ablation is on" is no longer without
1583
+ * them (CodeRabbit on #2925). */
1584
+ var PUT_LINT_NOTE = "These rows WERE written. The write gate refuses what would make a record unopenable — a missing required field, a value outside an enum, a mismatched primaryKey, a computed key, a colliding id under `create` — but it does not check the SHAPE of a value, so a wrong-shaped one is reported here rather than rejected. `getItems` surfaces the same finding as a `warning` (on a full listing, or when a requested id is missing), and publishing a shared app REFUSES the row outright. Fix the generator and rewrite them before writing the rest of the set.";
1569
1585
  /** Refuse an `itemsFile` larger than this, from `stat` and before any read.
1570
1586
  * The row cap alone cannot bound the work: the file has to be read and parsed
1571
1587
  * WHOLE before there are rows to count, so a huge blob is paid for in full
@@ -1734,6 +1750,26 @@ async function mergeWithExisting(collection, store, record, itemId) {
1734
1750
  ...record
1735
1751
  };
1736
1752
  }
1753
+ /** The strict-tier finding on a row the write gate LET THROUGH, or nothing.
1754
+ *
1755
+ * The gate stays exactly where it was — `lint-not-lock` is why the strict tier
1756
+ * exists (see `../core/recordZ`), and turning these into rejections would make
1757
+ * a collection whose legacy rows predate the typed rules unwritable. What was
1758
+ * missing is that the row's author never HEARD about it: `getItems` reports the
1759
+ * same finding and publishing a shared app refuses exactly these rows, so the
1760
+ * one surface that stayed silent was the one that could still fix the generator
1761
+ * before it wrote the other 719 rows (mulmoterminal#1763).
1762
+ *
1763
+ * Run against the record as WRITTEN (`toWrite`, post-merge, post-defaults) —
1764
+ * what a later read will lint is what landed, not what the caller sent. */
1765
+ function lintOf(record, itemId, schema, deps) {
1766
+ if (deps.ablateValidation) return {};
1767
+ const problem = validateRecordObject(record, itemId, schema, "strict");
1768
+ return problem ? { lint: {
1769
+ id: defangForPrompt(itemId),
1770
+ problem: defangForPrompt(problem)
1771
+ } } : {};
1772
+ }
1737
1773
  async function putOneItem(collection, store, write, record, mode, deps) {
1738
1774
  const { schema } = collection;
1739
1775
  const created = mode === "create" ? {
@@ -1759,7 +1795,10 @@ async function putOneItem(collection, store, write, record, mode, deps) {
1759
1795
  if (invalid) return reject(itemId, invalid);
1760
1796
  }
1761
1797
  const result = await write(itemId, toWrite, { refuseOverwrite: mode === "create" });
1762
- if (result.kind === "ok") return { written: result.itemId };
1798
+ if (result.kind === "ok") return {
1799
+ written: result.itemId,
1800
+ ...lintOf(toWrite, result.itemId, schema, deps)
1801
+ };
1763
1802
  if (result.kind === "invalid-id") return reject(itemId, `'${itemId}' is not a valid record id (letters/digits at the ends; -, _, or . inside; no '..' or path characters)`);
1764
1803
  if (result.kind === "conflict") return reject(itemId, `'${itemId}' already exists — mode "create" refuses overwrite; use "upsert" to update it`);
1765
1804
  return reject(itemId, "write refused: the collection's data dir escapes the workspace");
@@ -1932,23 +1971,51 @@ async function resolvePutRows(args, deps) {
1932
1971
  if (rows.length > 1e3) return `manageCollection: refused — ${rows.length} rows is over the putItems limit of ${MAX_PUT_ITEMS}. Nothing was written; split them across several calls.`;
1933
1972
  return rows;
1934
1973
  }
1935
- async function handlePutItems(collection, args, deps) {
1936
- const store = storeFor(collection, { workspaceRoot: deps.workspaceRoot });
1937
- const { write } = store;
1938
- if (!write) return `manageCollection: ${readOnlyRefusal(collection.slug)} (its records are the rows of '${collection.schema.dataSource?.path}'; edit that file to change the data).`;
1939
- const rows = await resolvePutRows(args, deps);
1940
- if (typeof rows === "string") return rows;
1974
+ /** The `lint` block of a putItems result, or nothing when every written row is
1975
+ * clean — absent rather than empty, so a clean call reads exactly as it always
1976
+ * did and the key's presence is itself the signal.
1977
+ *
1978
+ * `total` counts every flagged row and `rows` shows the first
1979
+ * `MAX_PUT_LINT` of them, so a capped report can never be read as a total. */
1980
+ function lintReport(lint) {
1981
+ if (lint.length === 0) return {};
1982
+ return { lint: {
1983
+ total: lint.length,
1984
+ note: PUT_LINT_NOTE,
1985
+ rows: lint.slice(0, 10)
1986
+ } };
1987
+ }
1988
+ /** Write the batch row by row, sorting each outcome into its own list. One row
1989
+ * at a time on purpose: a rejection is per row, so a bad row must not take the
1990
+ * batch with it. */
1991
+ async function putEachRow(collection, store, write, rows, mode, deps) {
1941
1992
  const written = [];
1942
1993
  const rejected = [];
1994
+ const lint = [];
1943
1995
  for (const record of rows) {
1944
- const outcome = await putOneItem(collection, store, write, record, args.mode, deps);
1996
+ const outcome = await putOneItem(collection, store, write, record, mode, deps);
1945
1997
  if (outcome.written) written.push(outcome.written);
1946
1998
  if (outcome.rejected) rejected.push(outcome.rejected);
1999
+ if (outcome.lint) lint.push(outcome.lint);
1947
2000
  }
2001
+ return {
2002
+ written,
2003
+ rejected,
2004
+ lint
2005
+ };
2006
+ }
2007
+ async function handlePutItems(collection, args, deps) {
2008
+ const store = storeFor(collection, { workspaceRoot: deps.workspaceRoot });
2009
+ const { write } = store;
2010
+ if (!write) return `manageCollection: ${readOnlyRefusal(collection.slug)} (its records are the rows of '${collection.schema.dataSource?.path}'; edit that file to change the data).`;
2011
+ const rows = await resolvePutRows(args, deps);
2012
+ if (typeof rows === "string") return rows;
2013
+ const { written, rejected, lint } = await putEachRow(collection, store, write, rows, args.mode, deps);
1948
2014
  return JSON.stringify({
1949
2015
  collection: collection.slug,
1950
2016
  written,
1951
- rejected
2017
+ rejected,
2018
+ ...lintReport(lint)
1952
2019
  });
1953
2020
  }
1954
2021
  /** Delete records by id, through the store — so it works on any writable
@@ -2158,7 +2225,7 @@ async function handlePutSchema(slug, schemaArg, deps) {
2158
2225
  written: true
2159
2226
  });
2160
2227
  }
2161
- var MANAGE_COLLECTION_PROMPT = "Use `manageCollection` instead of raw Read/Write/Edit when working with a collection's records OR its schema (raw file I/O stays available as the escape hatch). Before authoring or changing a collection's `schema.json`, call `schemaDocs` to load the field/DSL reference — the default reply is the core authoring guide plus a table of contents; fetch advanced sections (actions, bells, calendar/kanban views, dataSource, storage) by passing their heading as `topic` rather than dumping `topic: \"all\"`. Then read with `getSchema` and write with `putSchema` — `putSchema` validates the whole schema before writing and returns actionable errors instead of silently failing discovery's validation. `getItems` is the only way to see computed values — `derived` fields (e.g. a portfolio's value), `toggle` projections, and `embed` records are host-computed and never present in the stored JSON files. On large collections pass `ids` and/or `fields` to keep the result small. For a question that spans collections (\"which clients have unpaid invoices?\"), start with `getOntology`: it lists every collection with its primaryKey, record count, and outbound `ref`/`embed` relations, so you know which collections to join before reading any records. `putItems` validates every row against the schema before writing (required fields, enum values, primaryKey = record id) and returns `{ written, rejected }`; fix each rejected row using its `problem` text and retry just those rows. Never include computed fields in a row you write. When the rows come from a script rather than from you (a generated schedule, an imported set, anything past a few dozen records), write them to a JSON file UNDER THE WORKSPACE and pass its absolute path as `itemsFile` instead of `items` — the host reads the file, so the rows never pass through your context. Do NOT hand-transcribe a generated file into `items`, and never drive the collection by spawning the MCP bridge yourself. To update a few fields of an existing record, use `mode: \"merge\"` with a partial row ({ id, <changed fields> }) — the default upsert replaces the WHOLE record, so a partial upsert would silently erase every optional field it omits. `deleteItems` removes records by id and returns `{ deleted, rejected }`; an id that doesn't exist comes back rejected rather than counted as deleted, so check `rejected` before reporting a deletion as done. Answer aggregation questions (counts, sums, averages, group-bys) with `queryItems` on ANY collection — on a dataSource (CSV) collection it scans the whole file (getItems is row-capped, so aggregates computed from its output can be silently wrong on large files); on a file-backed collection it aggregates the enriched records, so computed fields (derived/rollup/toggle) are queryable columns.";
2228
+ var MANAGE_COLLECTION_PROMPT = "Use `manageCollection` instead of raw Read/Write/Edit when working with a collection's records OR its schema (raw file I/O stays available as the escape hatch). Before authoring or changing a collection's `schema.json`, call `schemaDocs` to load the field/DSL reference — the default reply is the core authoring guide plus a table of contents; fetch advanced sections (actions, bells, calendar/kanban views, dataSource, storage) by passing their heading as `topic` rather than dumping `topic: \"all\"`. Then read with `getSchema` and write with `putSchema` — `putSchema` validates the whole schema before writing and returns actionable errors instead of silently failing discovery's validation. `getItems` is the only way to see computed values — `derived` fields (e.g. a portfolio's value), `toggle` projections, and `embed` records are host-computed and never present in the stored JSON files. On large collections pass `ids` and/or `fields` to keep the result small. For a question that spans collections (\"which clients have unpaid invoices?\"), start with `getOntology`: it lists every collection with its primaryKey, record count, and outbound `ref`/`embed` relations, so you know which collections to join before reading any records. `putItems` GATES every row on what would make the record unopenable — required fields, enum values, primaryKey = record id — and returns `{ written, rejected }`; fix each rejected row using its `problem` text and retry just those rows. Never include computed fields in a row you write. That gate does NOT check the SHAPE of a value: a `datetime` written as an instant (`2026-08-17T15:00:00.000Z`, what `toISOString()` produces) rather than as a wall clock, a `number` holding something that is not a number at all, a `date` that is not a real day are all WRITTEN, and come back in a `lint` block beside `written`. Read it — a full `getItems` listing warns about the same rows and publishing a shared app REFUSES them, so a silent `lint` is the only proof the values are right. Before generating a large set, read the exact stored form of each type in the `Field types` section of `schemaDocs` (`topic: \"Field types\"` fetches just that one), then write ONE batch and check that `lint` is absent. `datetime` in particular is a local wall clock (`YYYY-MM-DDTHH:MM`, no timezone suffix), so `new Date(...).toISOString()` is wrong twice over — the suffix, and the hours the conversion moved. The one `Z`-suffixed datetime the strict tier accepts is a shared app's server-stamped instant, written by the SERVER with nine fractional digits; you never produce that value. When the rows come from a script rather than from you (a generated schedule, an imported set, anything past a few dozen records), write them to a JSON file UNDER THE WORKSPACE and pass its absolute path as `itemsFile` instead of `items` — the host reads the file, so the rows never pass through your context. Do NOT hand-transcribe a generated file into `items`, and never drive the collection by spawning the MCP bridge yourself. To update a few fields of an existing record, use `mode: \"merge\"` with a partial row ({ id, <changed fields> }) — the default upsert replaces the WHOLE record, so a partial upsert would silently erase every optional field it omits. `deleteItems` removes records by id and returns `{ deleted, rejected }`; an id that doesn't exist comes back rejected rather than counted as deleted, so check `rejected` before reporting a deletion as done. Answer aggregation questions (counts, sums, averages, group-bys) with `queryItems` on ANY collection — on a dataSource (CSV) collection it scans the whole file (getItems is row-capped, so aggregates computed from its output can be silently wrong on large files); on a file-backed collection it aggregates the enriched records, so computed fields (derived/rollup/toggle) are queryable columns.";
2162
2229
  /** Validate getItems' optional `ids`/`fields` args, then delegate. */
2163
2230
  async function dispatchGetItems(collection, args, deps) {
2164
2231
  const ids = optionalStringArray(args.ids, "ids");
@@ -2214,7 +2281,7 @@ async function dispatchManageCollection(deps, args) {
2214
2281
  }
2215
2282
  var MANAGE_COLLECTION_DEFINITION = {
2216
2283
  name: "manageCollection",
2217
- description: "Read and write a schema-driven collection through the host — both its records and its structure. getItems returns records WITH computed values (derived formulas, toggles, embeds) the stored JSON files don't contain; putItems validates each row against the schema before writing; deleteItems removes records by id. getOntology maps the whole workspace: every collection with its record count and outbound ref/embed relations — call it first for cross-collection questions. schemaDocs returns the collection-authoring reference — the core guide plus a table of contents by default; pass `topic` for a specific section. getSchema/putSchema read and validate-then-write the collection's schema.json. Prefer it over raw file I/O on collections.",
2284
+ description: "Read and write a schema-driven collection through the host — both its records and its structure. getItems returns records WITH computed values (derived formulas, toggles, embeds) the stored JSON files don't contain; putItems gates each row on required/enum/primaryKey before writing and lints the value shapes it does not gate; deleteItems removes records by id. getOntology maps the whole workspace: every collection with its record count and outbound ref/embed relations — call it first for cross-collection questions. schemaDocs returns the collection-authoring reference — the core guide plus a table of contents by default; pass `topic` for a specific section. getSchema/putSchema read and validate-then-write the collection's schema.json. Prefer it over raw file I/O on collections.",
2218
2285
  inputSchema: {
2219
2286
  type: "object",
2220
2287
  properties: {
@@ -2289,6 +2356,6 @@ function makeManageCollectionTool(deps = {}) {
2289
2356
  };
2290
2357
  }
2291
2358
  //#endregion
2292
- export { runCollectionQuery as A, firstMutateParamProblem as C, validateRecordObject as D, validateCollectionRecords as E, promptPathsFor as F, readCustomViewHtml as I, readCustomViewI18n as L, runQueryOverRows as M, buildActionSeedPrompt as N, compileRecordZ as O, buildCollectionActionSeedPrompt as P, readSkillTemplate as R, applyMutateAction as S, STORE_UNREADABLE as T, successorId as _, makeManageCollectionTool as a, buildWorkspaceOntology as b, deleteCollectionRefusalMessage as c, daysInMonth as d, formatCivil as f, resolveEvery as g, parseCivil as h, MAX_UNSELECTIVE_ITEMS as i, enrichItems as j, recordFieldProblem as k, advanceTriggerDate as l, maybeSpawnSuccessor as m, MAX_PUT_ITEMS as n, deleteCustomView as o, isTriggerDue as p, MAX_SCHEMA_ISSUES as r, deleteCollection as s, MAX_ITEMS_FILE_BYTES as t, computeSuccessor as u, ONE_SECOND_MS as v, MAX_RECORD_ISSUES as w, schemaRelations as x, computeCollectionIcon as y };
2359
+ export { recordFieldProblem as A, applyMutateAction as C, validateCollectionRecords as D, STORE_UNREADABLE as E, buildCollectionActionSeedPrompt as F, promptPathsFor as I, readCustomViewHtml as L, enrichItems as M, runQueryOverRows as N, validateRecordObject as O, buildActionSeedPrompt as P, readCustomViewI18n as R, schemaRelations as S, MAX_RECORD_ISSUES as T, resolveEvery as _, MAX_UNSELECTIVE_ITEMS as a, computeCollectionIcon as b, deleteCollection as c, computeSuccessor as d, daysInMonth as f, parseCivil as g, maybeSpawnSuccessor as h, MAX_SCHEMA_ISSUES as i, runCollectionQuery as j, compileRecordZ as k, deleteCollectionRefusalMessage as l, isTriggerDue as m, MAX_PUT_ITEMS as n, makeManageCollectionTool as o, formatCivil as p, MAX_PUT_LINT as r, deleteCustomView as s, MAX_ITEMS_FILE_BYTES as t, advanceTriggerDate as u, successorId as v, firstMutateParamProblem as w, buildWorkspaceOntology as x, ONE_SECOND_MS as y, readSkillTemplate as z };
2293
2360
 
2294
- //# sourceMappingURL=server-C-Ngd3g2.js.map
2361
+ //# sourceMappingURL=server-BvXO49Vj.js.map