@mulmoclaude/core 4.1.1 → 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.
- package/assets/helps/collection-skills.md +17 -6
- package/assets/helps/error-recovery.md +53 -0
- package/dist/collection/core/calendarGrid.d.ts +2 -2
- package/dist/collection/core/serverTime.d.ts +93 -0
- package/dist/collection/index.cjs +9 -2
- package/dist/collection/index.d.ts +1 -0
- package/dist/collection/index.js +3 -3
- package/dist/collection/registry/server/index.cjs +2 -2
- package/dist/collection/registry/server/index.js +2 -2
- package/dist/collection/server/index.cjs +4 -3
- package/dist/collection/server/index.js +4 -4
- package/dist/collection/server/manageTool.d.ts +24 -0
- package/dist/collection-watchers/index.cjs +4 -4
- package/dist/collection-watchers/index.js +4 -4
- package/dist/{discovery-B9EESjHP.cjs → discovery-Dl9nRa-m.cjs} +38 -20
- package/dist/discovery-Dl9nRa-m.cjs.map +1 -0
- package/dist/{discovery-D88x2DmW.js → discovery-P64fgtuD.js} +38 -20
- package/dist/discovery-P64fgtuD.js.map +1 -0
- package/dist/feeds/index.cjs +2 -2
- package/dist/feeds/index.js +2 -2
- package/dist/feeds/server/index.cjs +4 -4
- package/dist/feeds/server/index.js +4 -4
- package/dist/google/index.cjs +2 -2
- package/dist/google/index.js +2 -2
- package/dist/{ingestTypes-B312BwXg.js → ingestTypes-CIy1lHjf.js} +2 -2
- package/dist/{ingestTypes-B312BwXg.js.map → ingestTypes-CIy1lHjf.js.map} +1 -1
- package/dist/{ingestTypes-B4XfrUoq.cjs → ingestTypes-CaX7_s-K.cjs} +2 -2
- package/dist/{ingestTypes-B4XfrUoq.cjs.map → ingestTypes-CaX7_s-K.cjs.map} +1 -1
- package/dist/{itemId-DfTm08jm.js → itemId-BY2YPBkT.js} +200 -6
- package/dist/itemId-BY2YPBkT.js.map +1 -0
- package/dist/{itemId-Dzrr3vs5.cjs → itemId-CEQdK01z.cjs} +241 -5
- package/dist/itemId-CEQdK01z.cjs.map +1 -0
- package/dist/{promptSafety-CJ-sC1pX.js → promptSafety-Bp780ezk.js} +2 -2
- package/dist/{promptSafety-CJ-sC1pX.js.map → promptSafety-Bp780ezk.js.map} +1 -1
- package/dist/{promptSafety-CuczAN9d.cjs → promptSafety-C0BWDsfT.cjs} +2 -2
- package/dist/{promptSafety-CuczAN9d.cjs.map → promptSafety-C0BWDsfT.cjs.map} +1 -1
- package/dist/{server-L12n8We3.js → server-BvXO49Vj.js} +87 -17
- package/dist/server-BvXO49Vj.js.map +1 -0
- package/dist/{server-meS6kjBN.cjs → server-CF1qllGV.cjs} +92 -16
- package/dist/server-CF1qllGV.cjs.map +1 -0
- package/package.json +1 -1
- package/dist/discovery-B9EESjHP.cjs.map +0 -1
- package/dist/discovery-D88x2DmW.js.map +0 -1
- package/dist/itemId-DfTm08jm.js.map +0 -1
- package/dist/itemId-Dzrr3vs5.cjs.map +0 -1
- package/dist/server-L12n8We3.js.map +0 -1
- package/dist/server-meS6kjBN.cjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"promptSafety-CuczAN9d.cjs","names":[],"sources":["../src/collection/core/fieldDefaults.ts","../src/collection/core/actionVisible.ts","../src/collection/core/backlinks.ts","../src/collection/core/linkTargets.ts","../src/collection/core/where.ts","../src/collection/core/completion.ts","../src/collection/core/dynamicIcon.ts","../src/collection/core/derivedFormula.ts","../src/collection/core/ownProp.ts","../src/collection/core/deriveAll.ts","../src/collection/core/promptSafety.ts"],"sourcesContent":["// What a NEW record starts on (#2839). Shared so the Add form and\n// `putItems mode:\"create\"` agree — a default the UI pre-fills but the tool\n// ignores is worse than none, because only one of the two paths gets it.\n//\n// Only `enum` declares a `default` today. The other types are a separate\n// question (literals for scalars, `today` / `now` sentinels for dates) and\n// deliberately not modelled yet.\n\nimport type { CollectionFieldSpec, CollectionSchema } from \"./schema.js\";\n\n/** The starting value for a field, or null when it declares none.\n *\n * A `default` outside `values` resolves to null rather than being handed on.\n * `putSchema` refuses to write one, but the key was silently ignored before\n * #2839, so a stale value may already sit in a file that discovery still\n * loads — and putting an impossible value into a form yields a rejected save\n * the author cannot explain from what they see. */\nexport function fieldDefaultValue(field: CollectionFieldSpec): string | null {\n if (field.type !== \"enum\" || field.default === undefined) return null;\n return field.values.includes(field.default) ? field.default : null;\n}\n\n/** Every applicable default in a schema, keyed by field.\n *\n * Built through `Object.fromEntries` rather than by assigning into an\n * accumulator, because a field may legitimately be named `__proto__` —\n * `JSON.parse` hands that over as an OWN key, so it survives into the parsed\n * schema (the same reason the primary-field resolver reaches for\n * `Object.hasOwn`). `out[key] = value` on a plain object would run the\n * prototype setter for that one name and drop the default with no error\n * anywhere; `fromEntries` defines an own property either way (Codex review on\n * #2910). */\nexport function schemaDefaults(schema: CollectionSchema): Record<string, string> {\n const entries = Object.entries(schema.fields).map(([key, field]): [string, string | null] => [key, fieldDefaultValue(field)]);\n return Object.fromEntries(entries.filter((entry): entry is [string, string] => entry[1] !== null));\n}\n\n/** The first `default` that names something `values` does not offer, for the\n * WRITE path to refuse. Kept out of the parse so a file already carrying one\n * keeps loading — see `fieldDefaultValue`. */\nexport function firstUnknownDefault(schema: CollectionSchema): { key: string; value: string; values: string[] } | null {\n for (const [key, field] of Object.entries(schema.fields)) {\n if (field.type !== \"enum\" || field.default === undefined) continue;\n if (!field.values.includes(field.default)) return { key, value: field.default, values: field.values };\n }\n return null;\n}\n","// Pure `when`-predicate visibility helpers for schema-driven\n// collections — used both for action buttons and for conditionally\n// shown fields. Kept as their own module (no Vue) so CollectionView\n// can stay thin and the match semantics are pinned by unit tests.\n// Domain-free: the host compares the stringified record value against\n// the allowed set with no knowledge of what the field means.\n\nimport { fieldTextOrNull } from \"./fieldText\";\n\n/** A `when` predicate: render only when the open record's `field`\n * (stringified) is one of `in`. Shared shape for action buttons and\n * conditionally visible fields. */\nexport interface WhenPredicate {\n field: string;\n in: string[];\n}\n\n/** Core matcher:\n * - no `when` ⇒ always true (visible);\n * - otherwise true only when `record[when.field]` is present and its\n * stringified value is one of `when.in`.\n * A missing/undefined/null field is treated as \"not a match\"\n * (hidden), so a status-gated target never shows on a record that\n * lacks the status. */\nexport function whenMatches(when: WhenPredicate | undefined, record: Record<string, unknown>): boolean {\n if (!when) return true;\n // `fieldTextOrNull` rather than `String(...)`: an array/object field would\n // stringify to \"[object Object]\" and could match a `when.in` entry by\n // accident. No text ⇒ no match, same as an absent field.\n const text = fieldTextOrNull(record[when.field]);\n if (text === null) return false;\n return when.in.includes(text);\n}\n\n/** Minimal shape this helper needs from an action — the optional state\n * gate, whichever name its kind uses: `when` on the seeded kinds\n * (chat/agent), `require` on mutate. Accepts the full CollectionAction\n * union (each variant declares at most one of the two). */\nexport interface ActionWithWhen {\n when?: WhenPredicate | undefined;\n require?: WhenPredicate | undefined;\n}\n\n/** True when the action's button should render against `record` — and,\n * server-side, whether it may RUN (visibility is the authorization\n * rule, for every kind). */\nexport function actionVisible(action: ActionWithWhen, record: Record<string, unknown>): boolean {\n return whenMatches(action.when ?? action.require, record);\n}\n\n/** The run key naming one in-flight `kind: \"agent\"` action button —\n * written by the server's dispatch guard, read back by the client from\n * the detail response's `runningActions` to drive the spinner. ONE\n * builder (isomorphic) so the two sides can't drift. Collection-level\n * and per-record actions live in distinct namespaces so an id collision\n * between `actions` and `collectionActions` can't alias. */\nexport function agentActionRunKey(actionId: string, itemId?: string): string {\n return itemId === undefined ? `collection/${actionId}` : `item/${itemId}/${actionId}`;\n}\n\n/** Minimal shape this helper needs from a field spec — just its\n * optional `when` predicate. Accepts the full FieldSpec too. */\nexport interface FieldWithWhen {\n when?: WhenPredicate | undefined;\n}\n\n/** True when the field should render against `record`. A field with\n * no `when` is always shown; otherwise it's shown only when the\n * record matches (e.g. hide a rating field until `visited` is true).\n * Purely presentational — a hidden field's stored value is never\n * altered, so toggling the gate back on restores it. */\nexport function fieldVisible(field: FieldWithWhen, record: Record<string, unknown>): boolean {\n return whenMatches(field.when, record);\n}\n","// Pure resolution for `backlinks` fields (plan step ② of\n// plans/done/collection-ontology.md): the display-only reverse side of `ref`.\n// Both the server enrichment (`server/derive.ts`) and the client detail\n// view derive the row set through THESE helpers, so the LLM (getItems)\n// and the user (record panel) always see the same rows — the same\n// single-implementation rule `deriveAll` follows for formulas. No zod,\n// no I/O; safe for the browser barrel.\n\nimport { isRecord, isUnknownArray } from \"@mulmoclaude/common\";\n\nimport { whenMatches } from \"./actionVisible\";\nimport { fieldTextOrNull } from \"./fieldText\";\nimport type { CollectionFieldSpec, CollectionItem } from \"./schema\";\n\n/** The `backlinks` member of the field-spec union. */\nexport type BacklinksFieldSpec = Extract<CollectionFieldSpec, { type: \"backlinks\" }>;\n\n/** Does `item` reference `recordId` through `via`? Three shapes, tried in\n * order so the dotted form never regresses an existing flat key:\n *\n * - Exact key (`via` is an own property of `item`): the field holds the id\n * directly — compared as text, like every ref deref. This is checked FIRST\n * because `schemaZ` accepts any field name (`z.string()`), so a field\n * literally named `\"client.id\"` keeps its pre-nesting flat-match behaviour\n * instead of being reinterpreted as a table path. A field holding an\n * array/object has no id to compare, so it matches nothing (rather than\n * testing \"[object Object]\" against the record id).\n * - Nested ref (`via: \"<tableField>.<refColumn>\"`, split on the FIRST `.`,\n * only when no exact key exists): the source stores its ref one level down,\n * inside a `table` field's rows (the ontology scanner advertises these as\n * `${key}.${subKey}`). Matches when `item[tableField]` is an array and ANY\n * row's `refColumn` derefs to `recordId`. A record referencing the target in\n * several rows still matches once — the caller filters over source records,\n * each yielded at most once.\n * - No dot and no such key: matches nothing.\n *\n * Fail-soft throughout: a dotted `via` whose table field is absent / non-array,\n * or whose rows lack the column, matches nothing — the same \"a `via` that\n * doesn't resolve matches nothing\" contract as the flat case. Deeper nesting\n * (`a.b.c`) makes `b.c` the column name, which no row carries → no match. */\nexport function viaMatches(via: string, item: CollectionItem, recordId: string): boolean {\n if (Object.hasOwn(item, via)) return fieldTextOrNull(item[via]) === recordId;\n const dot = via.indexOf(\".\");\n if (dot === -1) return false;\n const tableField = via.slice(0, dot);\n const refColumn = via.slice(dot + 1);\n const rows = item[tableField];\n if (!isUnknownArray(rows)) return false;\n return rows.some((row) => isRecord(row) && fieldTextOrNull(row[refColumn]) === recordId);\n}\n\n/** The SOURCE records whose `via` field stores `recordId` (compared as\n * strings, like every ref deref), with the optional `filter` applied —\n * in the source items' given order. `via` may be a top-level column or a\n * `<tableField>.<refColumn>` path into a `table` field's rows (see\n * {@link viaMatches}). Fail-soft by construction: a `via` that doesn't\n * resolve on the source records simply matches nothing.\n * Callers pass DERIVED source records, so a `filter`/`display` on a\n * derived column works when its formula is SELF-CONTAINED (an invoice\n * `total` = sum over its own line items); a source column that derefs\n * yet another collection stays absent — the same each-record-derives-\n * against-itself rule ref targets follow. */\nexport function backlinkRows(spec: Pick<BacklinksFieldSpec, \"via\" | \"filter\">, recordId: string, sourceItems: CollectionItem[]): CollectionItem[] {\n if (!recordId) return [];\n return sourceItems.filter((item) => viaMatches(spec.via, item, recordId) && whenMatches(spec.filter, item));\n}\n\n/** Project one backlink row to the keys consumers surface: the source\n * collection's primaryKey (rows must stay addressable — it's the link\n * target) plus the declared `display` columns. Keys the row doesn't\n * carry are simply absent, mirroring `projectFields` in getItems. */\nexport function projectBacklinkRow(row: CollectionItem, display: readonly string[], primaryKey: string): CollectionItem {\n const keys = display.includes(primaryKey) ? display : [primaryKey, ...display];\n // Own-property only (matches `viaMatches` above): a display column named\n // `toString` must be absent, not project an inherited prototype function.\n return Object.fromEntries(keys.filter((key) => Object.hasOwn(row, key)).map((key) => [key, row[key]]));\n}\n\n/** The `rollup` member of the field-spec union. */\nexport type RollupFieldSpec = Extract<CollectionFieldSpec, { type: \"rollup\" }>;\n\n/** Numeric coercion shared by the strict record lint (`./recordZ`) and\n * rollup sums: a plain number, or a non-blank numeric string (renderers\n * coerce those via `Number(...)`, so they display fine). Anything else —\n * arrays (`[]` stringifies to `\"\"` = 0, `[42]` to `\"42\"`), booleans,\n * objects — is NaN. Lives here (zod-free) so both consumers share one\n * definition of \"numeric\". */\nexport function coerceNumeric(value: unknown): number {\n if (typeof value === \"number\") return value;\n if (typeof value === \"string\" && value.trim() !== \"\") return Number(value);\n return NaN;\n}\n\n/** The rollup aggregate over the matching source rows (plan step ⑤):\n * `count` = how many rows match; `sum` = the total of `column` over\n * them, skipping non-numeric / absent values (a partially-filled source\n * still sums what's there). An EMPTY match set is a real 0 — the\n * fail-soft null lives at the caller, for a source collection that\n * couldn't be resolved at all. Same derived-source-records contract as\n * `backlinkRows`: pass records derived against themselves, so summing a\n * self-contained derived column (an invoice `total`) works. */\nexport function rollupValue(spec: Pick<RollupFieldSpec, \"via\" | \"filter\" | \"op\" | \"column\">, recordId: string, sourceItems: CollectionItem[]): number {\n const rows = backlinkRows(spec, recordId, sourceItems);\n if (spec.op === \"count\") return rows.length;\n let total = 0;\n for (const row of rows) {\n const value = coerceNumeric(spec.column === undefined ? undefined : row[spec.column]);\n if (Number.isFinite(value)) total += value;\n }\n return total;\n}\n","// The single implementation of \"which linked collections must be\n// preloaded\" for a schema — walked identically by the server (derive /\n// enrich, `server/derive.ts`) and the client (linked-cache fetch,\n// collection-plugin). Both sides MUST agree: the server derefs exactly the\n// collections the client caches, so a new ref-shaped field type is taught\n// here once instead of drifting between two mirrored copies. Pure schema\n// walk, no zod / I/O — safe for the browser barrel.\n\nimport type { CollectionFieldSpec, CollectionSchema } from \"./schema\";\n\n/** Slugs of every collection referenced by a `ref` field — top-level and\n * one level into `table` sub-fields (nested tables are schema-rejected,\n * so a single recursion suffices). */\nexport function uniqueRefTargets(schema: CollectionSchema): string[] {\n const targets = new Set<string>();\n const walk = (fields: Record<string, CollectionFieldSpec>): void => {\n for (const field of Object.values(fields)) {\n if (field.type === \"ref\" && typeof field.to === \"string\" && field.to.length > 0) targets.add(field.to);\n if (field.type === \"table\" && field.of) walk(field.of);\n }\n };\n walk(schema.fields);\n return [...targets];\n}\n\n/** Slugs of every collection referenced by an `embed` field. Top-level\n * only — the schema rejects `embed` inside a table's `of`, so no\n * recursion. */\nexport function uniqueEmbedTargets(schema: CollectionSchema): string[] {\n const targets = new Set<string>();\n for (const field of Object.values(schema.fields)) {\n if (field.type === \"embed\" && typeof field.to === \"string\" && field.to.length > 0) targets.add(field.to);\n }\n return [...targets];\n}\n\n/** Slugs of every SOURCE collection a `backlinks` or `rollup` field\n * reverses over — loaded once (the two field kinds share one load).\n * Top-level only, like `embed` (the schema rejects both inside a table's\n * `of`). */\nexport function uniqueBacklinkSources(schema: CollectionSchema): string[] {\n const sources = new Set<string>();\n for (const field of Object.values(schema.fields)) {\n if ((field.type === \"backlinks\" || field.type === \"rollup\") && field.from.length > 0) sources.add(field.from);\n }\n return [...sources];\n}\n","// Pure SQL-like `where` predicate for `dynamicIcon` (see\n// `DynamicIconSource.where` / `DynamicIconRule.where` in `./schema`).\n// An AND of typed conditions — richer than the single-field `CollectionWhen`\n// used elsewhere (fields/actions via `./actionVisible`), which stays as-is\n// for its existing callers. No fs, no host state. The condition SHAPES are\n// derived from the zod source of truth in `./schemaZ` (type-only imports —\n// this evaluator stays zod-free at runtime).\n\nimport type { z } from \"zod\";\nimport type { ValueRefZ, WhereCondZ, WhereZ } from \"./schemaZ\";\n\n/** Reads the comparison value from a field instead of a schema literal:\n * - `record` set → another record: `recordsById[record][field]` (e.g. a\n * `_config` singleton's `defaultCity`, following a per-user setting);\n * - `record` omitted → the SAME record being matched (field-to-field, e.g.\n * `spent > budget`). */\nexport type ValueRef = z.infer<typeof ValueRefZ>;\n\n/** One typed condition: `record[field] <op> value`. The comparison value is\n * either a literal `value` (a plain string for every op except `in`, which\n * takes the allowed set) or a `valueFrom` reference resolved against the\n * `recordsById` map passed to `matchesWhere`. Exactly one of the two is\n * expected — enforced by zod at the schema boundary (`./schemaZ`), not\n * here. */\nexport type WhereCond = z.infer<typeof WhereCondZ>;\n\n/** Comparison operators one `WhereCond` may apply to `record[field]`. */\nexport type WhereOp = WhereCond[\"op\"];\n\n/** A `where` clause is the AND of its conditions — every one must match. */\nexport type Where = z.infer<typeof WhereZ>;\n\n/** True when `record[field]` is absent (`undefined`/`null`) — the only case\n * where `ne` and every other op disagree on the result. */\nfunction isMissing(raw: unknown): boolean {\n return raw === undefined || raw === null;\n}\n\n/** Own-property read for a user/LLM-controlled key (`cond.field`,\n * `valueFrom.record`, `valueFrom.field`). A bare `obj[key]` reaches inherited\n * Object.prototype members, so `field: \"toString\"` would read a function\n * instead of an absent value (spurious match), and `record: \"constructor\"`\n * would read the `Object` function whose `.name`/`.length` are plausible\n * bogus comparands — breaking the \"unresolved ⇒ never matches\" contract\n * (#2323). A prototype key resolves to `undefined` (absent). */\nfunction ownProp<T>(obj: Record<string, T>, key: string): T | undefined {\n return Object.hasOwn(obj, key) ? obj[key] : undefined;\n}\n\n/** The effective comparison value for `cond`: its literal `value`, or — for\n * a `valueFrom` reference — the target field read out of `recordsById`.\n * `undefined` means UNRESOLVED (no such record, or the field on it is\n * missing); the caller must treat that as \"never matches\", not as a\n * literal `undefined` value to compare against. */\nfunction resolveValue(cond: WhereCond, record: Record<string, unknown>, recordsById: Record<string, Record<string, unknown>>): string | string[] | undefined {\n if (!cond.valueFrom) return cond.value;\n const { record: refRecord, field } = cond.valueFrom;\n const target = refRecord === undefined ? record : ownProp(recordsById, refRecord);\n const raw = target === undefined ? undefined : ownProp(target, field);\n return isMissing(raw) ? undefined : String(raw);\n}\n\nfunction matchesNumericOp(operator: \"gt\" | \"gte\" | \"lt\" | \"lte\", left: number, right: number): boolean {\n if (operator === \"gt\") return left > right;\n if (operator === \"gte\") return left >= right;\n if (operator === \"lt\") return left < right;\n return left <= right;\n}\n\n/** `Number(\"\")` / `Number(\" \")` are `0`, not `NaN`, so treat a blank string\n * as non-numeric explicitly — an empty field must fail a numeric compare,\n * not read as zero. */\nfunction toNumber(raw: string): number {\n return raw.trim() === \"\" ? NaN : Number(raw);\n}\n\nfunction matchesNumeric(operator: \"gt\" | \"gte\" | \"lt\" | \"lte\", raw: string, value: string | string[]): boolean {\n if (Array.isArray(value)) return false;\n const left = toNumber(raw);\n const right = toNumber(value);\n if (Number.isNaN(left) || Number.isNaN(right)) return false;\n return matchesNumericOp(operator, left, right);\n}\n\n/** True when the present string `raw` satisfies `operator` against the\n * resolved `value` (field known to exist — MISSING is handled by the\n * caller before this runs, and an UNRESOLVED `valueFrom` never reaches\n * here either). */\nfunction matchesPresent(operator: WhereOp, raw: string, value: string | string[]): boolean {\n switch (operator) {\n case \"eq\":\n return raw === String(value);\n case \"ne\":\n return raw !== String(value);\n case \"in\":\n return Array.isArray(value) && value.includes(raw);\n case \"contains\":\n return raw.includes(String(value));\n case \"gt\":\n case \"gte\":\n case \"lt\":\n case \"lte\":\n return matchesNumeric(operator, raw, value);\n default:\n return false;\n }\n}\n\n/** True when `record` satisfies one condition, given `recordsById` to\n * resolve a `valueFrom` reference. Two independent MISSING cases, checked\n * in order:\n * - `record[cond.field]` absent (`undefined`/`null`) → matches only `ne`\n * (vacuously true — \"not equal to X\" holds when there's no value at\n * all); every other op is false. Unchanged from the literal-`value`\n * behaviour, regardless of whether `valueFrom` would also resolve.\n * - the resolved comparison value is UNRESOLVED (a `valueFrom` whose\n * target record/field doesn't exist) → false for EVERY op, including\n * `ne` — a broken reference must never spuriously match. */\nfunction matchesCond(cond: WhereCond, record: Record<string, unknown>, recordsById: Record<string, Record<string, unknown>>): boolean {\n const raw = ownProp(record, cond.field);\n if (isMissing(raw)) return cond.op === \"ne\";\n const value = resolveValue(cond, record, recordsById);\n if (value === undefined) return false;\n return matchesPresent(cond.op, String(raw), value);\n}\n\n/** True when `record` satisfies every condition in `where` (AND). An empty\n * `where` matches everything. `recordsById` — the source collection's\n * records keyed by primaryKey — resolves any `valueFrom` reference;\n * omitted (default `{}`) for callers with no cross-record lookups, in\n * which case every `valueFrom` condition is UNRESOLVED and so never\n * matches. */\nexport function matchesWhere(where: Where, record: Record<string, unknown>, recordsById: Record<string, Record<string, unknown>> = {}): boolean {\n return where.every((cond) => matchesCond(cond, record, recordsById));\n}\n","// The \"is this record done?\" predicate — THE single implementation,\n// shared by the notification reconciler (bell clearing, collection-watchers),\n// spawn (successor-predicate fallback, ../server/spawn.ts), and view-side\n// completion filters. Zod-free and I/O-free like the rest of `core/`, so\n// it is browser-safe through the collection barrel.\n//\n// Two completion forms (see `CollectionSchemaZ`'s completion refine):\n// - legacy pair: `completionField` names a stored field and\n// `completionDoneValues` lists the values that mean done —\n// done ⇔ `String(item[completionField])` ∈ `completionDoneValues`.\n// - flag form: `completionField` names a `flag` field (and\n// `completionDoneValues` is absent) — done ⇔ the flag's `where`\n// matches. Evaluated directly against the raw record here (NOT read\n// from a materialized value) because callers like the reconciler and\n// spawn work on records straight off disk, before any `deriveAll`\n// enrichment. That raw evaluation is CORRECT BY CONSTRUCTION: a\n// schema-level refine rejects a completion flag whose `where`\n// references computed fields, so every condition reads stored data.\n\nimport { fieldText, fieldTextOrNull } from \"./fieldText\";\nimport { matchesWhere, type Where } from \"./where\";\nimport type { CollectionFieldSpec, CollectionItem } from \"./schema\";\n\n/** The slice of a parsed schema the done predicate reads — minimal\n * structural shape (like `DerivableSchema`) so the client and server\n * `CollectionSchema` types both satisfy it as-is. */\nexport interface CompletionSchemaView {\n /** Optional so legacy-pair callers (and their test fixtures) that\n * never consult field specs keep working; only the flag form needs\n * to look the completion field up. */\n fields?: Record<string, { type: string; where?: Where }> | undefined;\n completionField?: string | undefined;\n completionDoneValues?: readonly string[] | undefined;\n}\n\n/** True iff the schema declares completion tracking AND `item` is done\n * under whichever completion form the schema uses (see module doc). */\nexport function itemIsDone(schema: CompletionSchemaView, item: Record<string, unknown>): boolean {\n const { completionField, completionDoneValues } = schema;\n if (!completionField) return false;\n const spec = schema.fields?.[completionField];\n if (spec?.type === \"flag\" && spec.where) return matchesWhere(spec.where, item);\n if (!completionDoneValues) return false;\n // An array/object field has no text form; treat it as \"not done\" rather than\n // letting \"[object Object]\" match a configured done-value.\n const text = fieldTextOrNull(item[completionField]);\n if (text === null) return false;\n return completionDoneValues.includes(text);\n}\n\n/** The schema slice `completionCoveredByFieldChip` reads: field kinds (it\n * inspects `boolean` / `toggle` variants' `field` / `onValue`) plus the\n * completion pair. Minimal structural shape so both the client and server\n * `CollectionSchema` types satisfy it as-is. */\nexport interface CompletionChipSchemaView {\n fields: Record<string, { type: string; field?: string; onValue?: string }>;\n completionField?: string | undefined;\n completionDoneValues?: readonly string[] | undefined;\n}\n\n/** True when an existing FIELD chip already expresses the legacy completion\n * predicate exactly, so a synthesized \"done\" chip would be a duplicate: a\n * boolean `completionField` (done ⇔ `\"true\"` ⇔ the boolean's own chip), or a\n * `toggle` projecting the `completionField` whose `onValue` is the single\n * done value (the todos-schema shape: toggle \"Done\" on `status` +\n * `completionDoneValues: [\"done\"]`). A superset pair (extra done values)\n * still synthesizes — no field chip covers it. */\nexport function completionCoveredByFieldChip(schema: CompletionChipSchemaView): boolean {\n const { completionField, completionDoneValues } = schema;\n if (completionDoneValues?.length !== 1) return false;\n const [doneValue] = completionDoneValues;\n if (schema.fields[completionField ?? \"\"]?.type === \"boolean\") return doneValue === \"true\";\n return Object.values(schema.fields).some((field) => field.type === \"toggle\" && field.field === completionField && field.onValue === doneValue);\n}\n\n/** Whether a `toggle` field reads as checked: its projected enum field currently\n * equals `onValue`. The toggle stores nothing of its own, so this reads the raw\n * projected value; a non-toggle field is never checked. */\nexport function toggleChecked(item: CollectionItem, field: CollectionFieldSpec): boolean {\n return field.type === \"toggle\" && fieldText(item[field.field]) === field.onValue;\n}\n\n/** A `flag` FIELD's boolean for one row, read off the already-enriched record\n * (so a flag computed from derived/rollup inputs is correct). Strict `=== true`\n * on purpose: a truthy non-boolean (\"yes\", 1) is NOT a set flag, so a stray\n * value can never render as an active flag. */\nexport function flagFieldValue(record: Record<string, unknown>, key: string): boolean {\n return record[key] === true;\n}\n\n/** One entry in the table's flag-filter menu: a real `flag` / `boolean` /\n * `toggle` field, or the synthesized legacy-completion chip (`synthetic`,\n * predicate = `itemIsDone`). */\nexport interface FlagChip {\n key: string;\n label: string;\n synthetic?: boolean;\n}\n\n/** The schema slice `chipMatches` reads: full field specs (it inspects the\n * `toggle` variant's `field` / `onValue`) plus the completion pair that\n * `itemIsDone` needs for the synthesized chip. */\nexport interface ChipMatchSchema {\n fields: Record<string, CollectionFieldSpec>;\n completionField?: string | undefined;\n completionDoneValues?: readonly string[] | undefined;\n}\n\n/** Whether one row satisfies a chip's predicate: `itemIsDone` for the\n * synthesized completion chip, the projected value for a `toggle`, the stored\n * boolean for a `boolean`, else the computed flag value. The flag branch reads\n * the ENRICHED record via the injected `deriveRecord` (kept a parameter so this\n * stays pure and framework-free). */\nexport function chipMatches(\n chip: FlagChip,\n schema: ChipMatchSchema,\n item: CollectionItem,\n deriveRecord: (item: CollectionItem) => Record<string, unknown>,\n): boolean {\n if (chip.synthetic) return itemIsDone(schema, item);\n const field = schema.fields[chip.key];\n if (field?.type === \"toggle\") return toggleChecked(item, field);\n if (field?.type === \"boolean\") return item[chip.key] === true;\n return flagFieldValue(deriveRecord(item), chip.key);\n}\n","// Pure resolver for a collection's dynamic launcher-shortcut icon (see\n// `CollectionSchema.dynamicIcon`). Selects one \"source\" record from a\n// (possibly cross-collection, optionally `where`-filtered) records pool,\n// then maps it through a first-match-wins rules list to an icon name.\n// No fs, no host state — the server-side compute\n// (`packages/core/src/collection/server/dynamicIcon.ts`) loads the source\n// collection's records and calls these.\n\nimport { fieldText } from \"./fieldText\";\nimport { matchesWhere } from \"./where\";\nimport type { CollectionFieldSpec, CollectionItem, CollectionSchema, DynamicIconSource, DynamicIconSpec } from \"./schema\";\n\n/** The record with the greatest `String(record[field])` (localeCompare) —\n * ties keep the first-seen record (stable left-to-right `reduce`). */\n// Seedless `reduce` is safe here: the sole caller (selectDynamicRecord) returns\n// null on an empty pool before this line, so an empty pool never arrives.\nfunction latestByField(pool: CollectionItem[], field: string): CollectionItem {\n return pool.reduce((latest, candidate) => (fieldText(candidate[field]).localeCompare(fieldText(latest[field])) > 0 ? candidate : latest));\n}\n\n/** Reduce `records` to the one record that decides the effective icon, per\n * `source`'s `where` filter + `from` strategy:\n * - pool = `source.where`-filtered records, or every record when unset;\n * - an empty pool resolves to `null` (no source record → fallback);\n * - `from: \"first\"` / `\"when\"` → the first pool record (storage order);\n * - `from: \"latest\"` (default), with `orderBy` given → the pool record\n * whose `String(record[orderBy])` sorts highest;\n * - `from: \"latest\"`, with no `orderBy` → the last pool record.\n * `recordsById` (the source collection's records keyed by primaryKey)\n * resolves any `valueFrom` reference inside `source.where`; omitted for\n * callers with no cross-record lookups. */\nexport function selectDynamicRecord(\n records: CollectionItem[],\n source: DynamicIconSource,\n orderBy: string | undefined,\n recordsById: Record<string, CollectionItem> = {},\n): CollectionItem | null {\n const { where } = source;\n const pool = where ? records.filter((record) => matchesWhere(where, record, recordsById)) : records;\n const [first] = pool;\n if (!first) return null;\n if (source.from === \"first\" || source.from === \"when\") return first;\n return orderBy ? latestByField(pool, orderBy) : (pool[pool.length - 1] ?? null);\n}\n\n/** Map a resolved source record to the effective icon: `spec.fallback`\n * (or the collection's own static `icon`) when there's no record or no\n * rule matches; otherwise the `icon` of the first rule whose `where`\n * matches the record. `recordsById` resolves any `valueFrom` reference\n * inside a rule's `where`, same as `selectDynamicRecord`. */\nexport function resolveIcon(\n record: CollectionItem | null,\n spec: DynamicIconSpec,\n staticIcon: string,\n recordsById: Record<string, CollectionItem> = {},\n): string {\n const fallback = spec.fallback ?? staticIcon;\n if (!record) return fallback;\n const matched = spec.rules.find((rule) => matchesWhere(rule.where, record, recordsById));\n return matched ? matched.icon : fallback;\n}\n\nconst isDateLikeField = (field: CollectionFieldSpec): boolean => field.type === \"date\" || field.type === \"datetime\";\n\n/** The first field key (declaration order) whose type is `date` or\n * `datetime` — the default `orderBy` for `from: \"latest\"` when a\n * `DynamicIconSource` doesn't name one. `undefined` when the schema has\n * no date-like field. */\nexport function firstDateField(schema: CollectionSchema): string | undefined {\n return Object.entries(schema.fields).find(([, field]) => isDateLikeField(field))?.[0];\n}\n","// Tiny expression evaluator for the `derived` field type on\n// schema-driven collections (see plans/done/feat-mc-invoice.md).\n//\n// Grammar (recursive-descent, no precedence climbing — six\n// non-terminals total):\n//\n// expr := term (('+' | '-') term)*\n// term := factor (('*' | '/') factor)*\n// factor := number | sumCall | refAccess | identifier | '(' expr ')'\n// sumCall:= 'sum' '(' sumArg ')'\n// sumArg := tableCol (('*' | '/') tableCol)* // e.g. lineItems[].quantity * lineItems[].rate\n// tableCol := identifier '[]' '.' identifier\n// refAccess := identifier '.' identifier // e.g. ticker.price — deref a ref field into its target record\n//\n// `identifier` accepts top-level field names (single segment).\n// Inside `sumArg`, identifiers are the `<table>[].col` form.\n// A two-segment `<field>.<col>` at factor level is a *ref deref*:\n// `<field>` must be a `ref`-typed field on this record (its stored\n// value is the target item's slug), and `<col>` is a numeric column\n// read from that target record. The caller resolves the target into\n// `ctx.refs` (it owns the schema + the loaded target collection);\n// the evaluator stays pure and never does I/O.\n//\n// What's deliberately NOT supported (and would parse-error rather\n// than silently misbehave):\n// - String literals, boolean operators, comparisons, conditionals\n// - Nested function calls beyond `sum(...)`\n// - Anything in the record that isn't a number / table-of-objects\n//\n// All evaluation is pure — no eval(), no Function constructor.\n// Returns `null` on any failure (parse error, unbound identifier,\n// non-finite arithmetic). The caller renders `null` as em-dash in\n// the table cell + form display.\n\nimport { isObj, isRecord, isUnknownArray } from \"@mulmoclaude/common\";\n\nexport interface FormulaContext {\n /** The record being evaluated. For derived fields in the form,\n * this is the live draft (text + table both converted via the\n * same `draftToRecord` pipeline). For the main table cell,\n * this is the persisted item. */\n record: Record<string, unknown>;\n /** Resolved ref-target records for THIS row, keyed by the local\n * `ref` field name. The caller (which has the schema + the linked\n * collection's items loaded) maps each ref field's stored slug to\n * the full target record and passes it here, so a `<field>.<col>`\n * formula can read a numeric column off the referenced record\n * (e.g. `shares * ticker.price`). A missing key or `null` value\n * (unknown field / dangling slug) makes that deref evaluate to\n * NaN → the whole formula returns `null` → em-dash, consistent\n * with every other failure mode. Absent ⇒ no refs available. */\n refs?: Record<string, Record<string, unknown> | null>;\n}\n\nexport function evaluateDerived(formula: string, ctx: FormulaContext): number | null {\n let tokens: Token[];\n try {\n tokens = tokenize(formula);\n } catch {\n return null;\n }\n // eslint-disable-next-line @typescript-eslint/no-use-before-define -- Parser class is defined later in the file (grouped with its AST + evaluator); evaluateDerived runs after module init so the TDZ concern doesn't apply.\n const parser = new Parser(tokens);\n let ast: Node;\n try {\n ast = parser.parseExpr();\n if (!parser.atEnd()) return null; // trailing junk\n } catch {\n return null;\n }\n const value = evaluate(ast, ctx);\n return Number.isFinite(value) ? value : null;\n}\n\n// ─── Tokens ────────────────────────────────────────────────\n\ntype PunctKind = \"(\" | \")\" | \"+\" | \"-\" | \"*\" | \"/\" | \"[]\" | \".\";\ntype TokenKind = \"number\" | \"ident\" | PunctKind;\n\n// Discriminated so a `number` token's payload is a number and an\n// `ident` token's payload is a string, with no reader having to assert it.\ntype Token = { kind: \"number\"; value: number } | { kind: \"ident\"; value: string } | { kind: PunctKind };\n\nconst SINGLE_CHAR_PUNCT: readonly PunctKind[] = [\"(\", \")\", \"+\", \"-\", \"*\", \"/\", \".\"];\n\nfunction isSingleCharPunct(char: string): char is PunctKind {\n return SINGLE_CHAR_PUNCT.some((punct) => punct === char);\n}\n\ninterface Cursor {\n input: string;\n index: number;\n}\n\nfunction consumeWhitespace(cur: Cursor): boolean {\n const char = cur.input[cur.index];\n if (char === \" \" || char === \"\\t\" || char === \"\\n\") {\n cur.index++;\n return true;\n }\n return false;\n}\n\nfunction consumeNumber(cur: Cursor): Token | null {\n const char = cur.input[cur.index] ?? \"\";\n const next = cur.input[cur.index + 1] ?? \"\";\n if (!isDigit(char) && !(char === \".\" && isDigit(next))) return null;\n let raw = \"\";\n while (cur.index < cur.input.length) {\n const here = cur.input[cur.index] ?? \"\";\n if (!isDigit(here) && here !== \".\") break;\n raw += here;\n cur.index++;\n }\n const num = Number(raw);\n if (!Number.isFinite(num)) throw new Error(\"bad number\");\n return { kind: \"number\", value: num };\n}\n\nfunction consumeIdent(cur: Cursor): Token | null {\n const char = cur.input[cur.index] ?? \"\";\n if (!isIdentStart(char)) return null;\n let raw = \"\";\n while (cur.index < cur.input.length && isIdentChar(cur.input[cur.index] ?? \"\")) {\n raw += cur.input[cur.index];\n cur.index++;\n }\n return { kind: \"ident\", value: raw };\n}\n\nfunction consumePunct(cur: Cursor): Token | null {\n const char = cur.input[cur.index] ?? \"\";\n if (char === \"[\" && cur.input[cur.index + 1] === \"]\") {\n cur.index += 2;\n return { kind: \"[]\" };\n }\n if (isSingleCharPunct(char)) {\n cur.index++;\n return { kind: char };\n }\n return null;\n}\n\nfunction tokenize(input: string): Token[] {\n const tokens: Token[] = [];\n const cur: Cursor = { input, index: 0 };\n while (cur.index < input.length) {\n if (consumeWhitespace(cur)) continue;\n // Number FIRST so a leading-dot literal (`.25`) isn't split by\n // the `.` punctuation branch.\n const numTok = consumeNumber(cur);\n if (numTok) {\n tokens.push(numTok);\n continue;\n }\n const punctTok = consumePunct(cur);\n if (punctTok) {\n tokens.push(punctTok);\n continue;\n }\n const identTok = consumeIdent(cur);\n if (identTok) {\n tokens.push(identTok);\n continue;\n }\n throw new Error(`unexpected char ${input[cur.index]}`);\n }\n return tokens;\n}\n\nfunction isDigit(char: string): boolean {\n return char >= \"0\" && char <= \"9\";\n}\nfunction isIdentStart(char: string): boolean {\n return (char >= \"a\" && char <= \"z\") || (char >= \"A\" && char <= \"Z\") || char === \"_\";\n}\nfunction isIdentChar(char: string): boolean {\n return isIdentStart(char) || isDigit(char);\n}\n\n// ─── AST + Parser ───────────────────────────────────────────\n\ntype AdditiveOperator = \"+\" | \"-\";\ntype MultiplicativeOperator = \"*\" | \"/\";\ntype BinaryOperator = AdditiveOperator | MultiplicativeOperator;\n\ntype Node =\n | { kind: \"num\"; value: number }\n | { kind: \"ident\"; name: string }\n | { kind: \"ref\"; field: string; col: string }\n | { kind: \"binop\"; operator: BinaryOperator; left: Node; right: Node }\n | { kind: \"sum\"; arg: SumArg };\n\nfunction matchAdditive(kind: TokenKind | undefined): AdditiveOperator | null {\n return kind === \"+\" || kind === \"-\" ? kind : null;\n}\n\nfunction matchMultiplicative(kind: TokenKind | undefined): MultiplicativeOperator | null {\n return kind === \"*\" || kind === \"/\" ? kind : null;\n}\n\ninterface TableCol {\n table: string;\n col: string;\n}\n\ninterface SumArg {\n // factors multiplied/divided together; each is a (tableName, colName) ref into a row.\n factors: TableCol[];\n /** Operators between factors: length = factors.length - 1; each\n * is \"*\" or \"/\". For a single-factor sum (`sum(lineItems[].amount)`)\n * this is empty. */\n operators: MultiplicativeOperator[];\n}\n\nclass Parser {\n private cursor = 0;\n constructor(private readonly tokens: Token[]) {}\n\n atEnd(): boolean {\n return this.cursor >= this.tokens.length;\n }\n private peek(): Token | undefined {\n return this.tokens[this.cursor];\n }\n private consume(): Token {\n const tok = this.tokens[this.cursor++];\n if (!tok) throw new Error(\"unexpected end of input\");\n return tok;\n }\n private expectPunct(kind: PunctKind): void {\n const tok = this.consume();\n if (tok.kind !== kind) throw new Error(`expected ${kind}, got ${tok.kind}`);\n }\n private expectIdent(): string {\n const tok = this.consume();\n if (tok.kind !== \"ident\") throw new Error(`expected ident, got ${tok.kind}`);\n return tok.value;\n }\n private takeOperator<Op extends BinaryOperator>(match: (kind: TokenKind | undefined) => Op | null): Op | null {\n const operator = match(this.peek()?.kind);\n if (operator) this.consume();\n return operator;\n }\n /** Yields each operator of a chain, consuming it. Iteration keeps the caller's\n * stack depth constant — a formula is user input, so its length must not\n * decide whether we overflow. */\n private *operatorRun<Op extends BinaryOperator>(match: (kind: TokenKind | undefined) => Op | null): Generator<Op> {\n while (true) {\n const operator = this.takeOperator(match);\n if (!operator) return;\n yield operator;\n }\n }\n\n private parseChain<Op extends BinaryOperator>(match: (kind: TokenKind | undefined) => Op | null, parseOperand: () => Node): Node {\n const left = parseOperand();\n const rest: { operator: Op; right: Node }[] = [];\n for (const operator of this.operatorRun(match)) {\n rest.push({ operator, right: parseOperand() });\n }\n return rest.reduce<Node>((accumulated, { operator, right }) => ({ kind: \"binop\", operator, left: accumulated, right }), left);\n }\n\n parseExpr(): Node {\n return this.parseChain(matchAdditive, () => this.parseTerm());\n }\n\n private parseTerm(): Node {\n return this.parseChain(matchMultiplicative, () => this.parseFactor());\n }\n\n private parseFactor(): Node {\n const tok = this.peek();\n if (!tok) throw new Error(\"unexpected end in factor\");\n if (tok.kind === \"number\") {\n this.consume();\n return { kind: \"num\", value: tok.value };\n }\n if (tok.kind === \"(\") {\n this.consume();\n const inner = this.parseExpr();\n this.expectPunct(\")\");\n return inner;\n }\n if (tok.kind === \"ident\") return this.parseIdentFactor(tok.value);\n throw new Error(`unexpected token ${tok.kind} in factor`);\n }\n\n private parseIdentFactor(name: string): Node {\n // sum(...) — only function call we support\n if (name === \"sum\" && this.tokens[this.cursor + 1]?.kind === \"(\") {\n this.consume(); // ident\n this.expectPunct(\"(\");\n const arg = this.parseSumArg();\n this.expectPunct(\")\");\n return { kind: \"sum\", arg };\n }\n this.consume(); // ident\n // ref deref: `<field>.<col>` (e.g. ticker.price). The table-row\n // form `<table>[].col` only appears inside sum(), so a `.`\n // immediately after a top-level ident is unambiguously a ref\n // dereference here.\n if (this.peek()?.kind === \".\") {\n this.consume(); // '.'\n return { kind: \"ref\", field: name, col: this.expectIdent() };\n }\n return { kind: \"ident\", name };\n }\n\n private parseSumArg(): SumArg {\n const factors = [this.parseTableCol()];\n const operators: MultiplicativeOperator[] = [];\n for (const operator of this.operatorRun(matchMultiplicative)) {\n operators.push(operator);\n factors.push(this.parseTableCol());\n }\n return { factors, operators };\n }\n\n private parseTableCol(): TableCol {\n const table = this.expectIdent();\n this.expectPunct(\"[]\");\n this.expectPunct(\".\");\n return { table, col: this.expectIdent() };\n }\n}\n\n// ─── Evaluator ──────────────────────────────────────────────\n\ntype LeafNode = Exclude<Node, { kind: \"binop\" }>;\n\n/** Post-order traversal step: either a subtree still to visit, or the operator\n * whose two operands are already on the value stack. */\ntype EvalStep = { kind: \"visit\"; node: Node } | { kind: \"apply\"; operator: BinaryOperator };\n\n/** Walks the tree with an explicit stack. `a + b + c + …` folds\n * left-associatively, so the AST's left spine is as deep as the formula is\n * long — recursing here would let a long (but valid) user-written formula\n * blow the call stack, and the RangeError would escape `evaluateDerived`'s\n * null contract into the caller. */\nfunction evaluate(root: Node, ctx: FormulaContext): number {\n const steps: EvalStep[] = [{ kind: \"visit\", node: root }];\n const values: number[] = [];\n for (let step = steps.pop(); step; step = steps.pop()) {\n if (step.kind === \"apply\") {\n values.push(applyPending(step.operator, values));\n } else if (step.node.kind === \"binop\") {\n const { operator, left, right } = step.node;\n // Popped in reverse, so the left operand lands on `values` first.\n steps.push({ kind: \"apply\", operator }, { kind: \"visit\", node: right }, { kind: \"visit\", node: left });\n } else {\n values.push(evaluateLeaf(step.node, ctx));\n }\n }\n const result = values.pop();\n return result !== undefined && values.length === 0 ? result : Number.NaN;\n}\n\nfunction applyPending(operator: BinaryOperator, values: number[]): number {\n const right = values.pop();\n const left = values.pop();\n // Both operands are pushed before their apply step, so a gap means a\n // malformed traversal — fail soft to NaN like every other bad value.\n if (left === undefined || right === undefined) return Number.NaN;\n return applyBinop(operator, left, right);\n}\n\nfunction evaluateLeaf(node: LeafNode, ctx: FormulaContext): number {\n if (node.kind === \"num\") return node.value;\n if (node.kind === \"ident\") return toFiniteNumber(ctx.record[node.name]);\n if (node.kind === \"ref\") {\n // `<field>.<col>`: read `col` off the resolved target record the\n // caller put in ctx.refs. Unknown field / dangling slug → null →\n // NaN, so the whole formula fails soft to an em-dash.\n const target = ctx.refs?.[node.field] ?? null;\n if (!target) return Number.NaN;\n return toFiniteNumber(target[node.col]);\n }\n return evaluateSum(node.arg, ctx);\n}\n\nfunction applyBinop(operator: BinaryOperator, left: number, right: number): number {\n if (!Number.isFinite(left) || !Number.isFinite(right)) return Number.NaN;\n if (operator === \"+\") return left + right;\n if (operator === \"-\") return left - right;\n if (operator === \"*\") return left * right;\n // operator === \"/\"\n if (right === 0) return Number.NaN;\n return left / right;\n}\n\nfunction evaluateSum(arg: SumArg, ctx: FormulaContext): number {\n const first = arg.factors.at(0);\n if (!first) return 0;\n // All factors must reference the SAME table (you can't multiply\n // a row from lineItems against a row from another table — the\n // semantics would be ambiguous). Reject mismatch.\n if (arg.factors.some((factor) => factor.table !== first.table)) return Number.NaN;\n const rows = ctx.record[first.table];\n if (!isUnknownArray(rows)) return 0;\n // A NaN from any row poisons the total, which the caller turns into null.\n return rows.filter(isObj).reduce((total, row) => total + rowProduct(row, arg), 0);\n}\n\nfunction rowProduct(row: object, { factors, operators }: SumArg): number {\n const [first, ...rest] = factors;\n if (!first) return Number.NaN;\n return rest.reduce(\n (product, factor, index) => {\n // The parser pushes one operator per extra factor, so a gap here means a\n // malformed SumArg — fail soft to NaN like every other bad value.\n const operator = operators[index];\n return operator ? applyBinop(operator, product, columnNumber(row, factor.col)) : Number.NaN;\n },\n columnNumber(row, first.col),\n );\n}\n\nfunction columnNumber(row: object, col: string): number {\n // An array row has no named column, so it fails soft like any other bad value.\n return isRecord(row) ? toFiniteNumber(row[col]) : Number.NaN;\n}\n\nfunction toFiniteNumber(value: unknown): number {\n if (typeof value === \"number\") return Number.isFinite(value) ? value : Number.NaN;\n if (typeof value === \"string\" && value.length > 0) {\n const num = Number(value);\n return Number.isFinite(num) ? num : Number.NaN;\n }\n return Number.NaN;\n}\n","// Own-property read for a plain object indexed by a user/LLM/feed-controlled\n// key. Extracted so the ref/embed resolution in `deriveAll` and `derive`\n// share one guard (where.ts keeps its own private copy for the dynamicIcon\n// `where` evaluator — #2443).\n\n/** Read `obj[key]` only when it is an OWN property. A bare `obj[key]` reaches\n * inherited Object.prototype members, so a dangling ref/embed id like\n * `\"constructor\"` or `\"__proto__\"` resolves to a prototype value (the\n * `Object` function) instead of being absent — breaking the \"missing target\n * ⇒ null (em-dash)\" fail-soft contract and putting a non-serializable\n * function into the API response, where `JSON.stringify` drops the key\n * entirely (#2322). A prototype key resolves to `undefined`; a record whose\n * id is legitimately `\"constructor\"` (an own key) still resolves. */\nexport function ownProp<T>(obj: Record<string, T>, key: string): T | undefined {\n return Object.hasOwn(obj, key) ? obj[key] : undefined;\n}\n","// The derived-field saturation loop for schema-driven collections,\n// extracted from `composables/collections/useCollectionRendering.ts` so\n// the server (manageCollection getItems enrichment) and the client\n// (table cells, form display) evaluate formulas through ONE\n// implementation — if the two ever diverged, the UI and the LLM would\n// disagree on a number. Pure module: no Vue, no I/O.\n//\n// Like `actionVisible.ts`, the input types are minimal structural\n// shapes so both the client `FieldSpec`/`CollectionSchema`\n// (src/components/collectionTypes.ts) and the server\n// `CollectionFieldSpec`/`CollectionSchema`\n// (server/workspace/collections/types.ts) satisfy them as-is.\n\nimport { evaluateDerived, type FormulaContext } from \"./derivedFormula\";\nimport { ownProp } from \"./ownProp\";\nimport { matchesWhere, type Where } from \"./where\";\n\n/** Minimal field shape the derive loop needs — accepts both the client\n * FieldSpec and the server CollectionFieldSpec. */\nexport interface DerivableFieldSpec {\n type: string;\n /** When type === \"ref\": slug of the target collection. */\n to?: string;\n /** When type === \"derived\": formula evaluated against the record. */\n formula?: string;\n /** When type === \"flag\": predicate matched against the record. */\n where?: Where;\n}\n\n/** Minimal schema shape: just the ordered field map. */\nexport interface DerivableSchema {\n fields: Record<string, DerivableFieldSpec>;\n}\n\nexport type DerivableRecord = Record<string, unknown>;\n\n/** Per-target-collection cache of loaded referenced records:\n * target collection slug → item slug → full record. Mirrors the\n * client's `RefRecordCache` / the server's enrichment loader. */\nexport type DeriveRefRecords = Record<string, Record<string, DerivableRecord>>;\n\n/** Map each `ref` field's stored slug to its loaded target record (or\n * null when dangling / not loaded), keyed by the LOCAL field name —\n * the shape `evaluateDerived` reads for `<field>.<col>` derefs. */\nexport function resolveRowRefs(schema: DerivableSchema, record: DerivableRecord, refRecords: DeriveRefRecords): NonNullable<FormulaContext[\"refs\"]> {\n const refs: NonNullable<FormulaContext[\"refs\"]> = {};\n for (const [key, field] of Object.entries(schema.fields)) {\n if (field.type !== \"ref\" || !field.to) continue;\n const slug = record[key];\n const targets = ownProp(refRecords, field.to);\n refs[key] = typeof slug === \"string\" && targets ? (ownProp(targets, slug) ?? null) : null;\n }\n return refs;\n}\n\n/** True for the field types the saturation loop below (re)computes:\n * `derived` formulas and `flag` predicates. */\nfunction isLoopComputed(field: DerivableFieldSpec): boolean {\n return field.type === \"derived\" || field.type === \"flag\";\n}\n\n/** The value one loop-computed field takes against the current\n * `enriched` record: a `derived` formula result (`null` = failed) or a\n * `flag` predicate match (total — always a boolean). `undefined` for\n * every other field type (nothing to compute). */\nfunction computeFieldValue(\n field: DerivableFieldSpec,\n enriched: DerivableRecord,\n refs: NonNullable<FormulaContext[\"refs\"]>,\n): number | boolean | null | undefined {\n if (field.type === \"derived\" && field.formula) return evaluateDerived(field.formula, { record: enriched, refs });\n if (field.type === \"flag\" && field.where) return matchesWhere(field.where, enriched);\n return undefined;\n}\n\n/** Evaluate every `derived` and `flag` field against `base`, saturating\n * so a computed field can read another one computed in an earlier pass\n * (`subtotal → tax → total` converges in ≤ field-count passes; a flag\n * may read a derived value, or another flag via its stringified\n * boolean). Cycles can't loop forever — passes are bounded by the\n * number of computed fields and the loop breaks as soon as a pass\n * changes nothing. Failed formulas stay ABSENT (the UI renders them as\n * em-dash); flags are total (always true/false). Returns a copy;\n * `base` is never mutated.\n *\n * Computed keys already present in `base` are stripped before\n * evaluation: computed output is host-truth, never persisted-input\n * fallback. A record JSON can carry a stale (or forged) computed value\n * — raw Write/Edit, legacy data — and without the strip, a failing\n * formula would silently surface that value as if the host computed\n * it. */\nexport function deriveAll(schema: DerivableSchema, base: DerivableRecord, refRecords: DeriveRefRecords): DerivableRecord {\n const computedKeys = new Set(\n Object.entries(schema.fields)\n .filter(([, field]) => isLoopComputed(field))\n .map(([key]) => key),\n );\n const enriched: DerivableRecord = Object.fromEntries(Object.entries(base).filter(([key]) => !computedKeys.has(key)));\n const refs = resolveRowRefs(schema, base, refRecords);\n for (let pass = 0; pass < computedKeys.size; pass++) {\n let mutated = false;\n for (const [key, field] of Object.entries(schema.fields)) {\n const next = computeFieldValue(field, enriched, refs);\n if (next !== undefined && next !== null && enriched[key] !== next) {\n enriched[key] = next;\n mutated = true;\n }\n }\n if (!mutated) break;\n }\n return enriched;\n}\n","// Neutralize structural prompt-injection vectors in a short, record-derived\n// string before it rides into an LLM-facing prompt: strip angle brackets,\n// defang backticks / `${` template openings, collapse whitespace (so an\n// embedded newline can't fabricate a pseudo-instruction on its own line), and\n// clip to a small budget. Mirrors the host server's own defang so the two\n// can't drift (#1677).\n\n/** Max chars kept — the first batch of a validation issue is enough to act on. */\nconst DEFANG_MAX_LEN = 200;\n\nexport function defangForPrompt(value: string): string {\n return value.replace(/[<>]/g, \"\").replace(/`/g, \"'\").replace(/\\$\\{/g, \"$ {\").replace(/\\s+/g, \" \").slice(0, DEFANG_MAX_LEN);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"promptSafety-C0BWDsfT.cjs","names":[],"sources":["../src/collection/core/fieldDefaults.ts","../src/collection/core/actionVisible.ts","../src/collection/core/backlinks.ts","../src/collection/core/linkTargets.ts","../src/collection/core/where.ts","../src/collection/core/completion.ts","../src/collection/core/dynamicIcon.ts","../src/collection/core/derivedFormula.ts","../src/collection/core/ownProp.ts","../src/collection/core/deriveAll.ts","../src/collection/core/promptSafety.ts"],"sourcesContent":["// What a NEW record starts on (#2839). Shared so the Add form and\n// `putItems mode:\"create\"` agree — a default the UI pre-fills but the tool\n// ignores is worse than none, because only one of the two paths gets it.\n//\n// Only `enum` declares a `default` today. The other types are a separate\n// question (literals for scalars, `today` / `now` sentinels for dates) and\n// deliberately not modelled yet.\n\nimport type { CollectionFieldSpec, CollectionSchema } from \"./schema.js\";\n\n/** The starting value for a field, or null when it declares none.\n *\n * A `default` outside `values` resolves to null rather than being handed on.\n * `putSchema` refuses to write one, but the key was silently ignored before\n * #2839, so a stale value may already sit in a file that discovery still\n * loads — and putting an impossible value into a form yields a rejected save\n * the author cannot explain from what they see. */\nexport function fieldDefaultValue(field: CollectionFieldSpec): string | null {\n if (field.type !== \"enum\" || field.default === undefined) return null;\n return field.values.includes(field.default) ? field.default : null;\n}\n\n/** Every applicable default in a schema, keyed by field.\n *\n * Built through `Object.fromEntries` rather than by assigning into an\n * accumulator, because a field may legitimately be named `__proto__` —\n * `JSON.parse` hands that over as an OWN key, so it survives into the parsed\n * schema (the same reason the primary-field resolver reaches for\n * `Object.hasOwn`). `out[key] = value` on a plain object would run the\n * prototype setter for that one name and drop the default with no error\n * anywhere; `fromEntries` defines an own property either way (Codex review on\n * #2910). */\nexport function schemaDefaults(schema: CollectionSchema): Record<string, string> {\n const entries = Object.entries(schema.fields).map(([key, field]): [string, string | null] => [key, fieldDefaultValue(field)]);\n return Object.fromEntries(entries.filter((entry): entry is [string, string] => entry[1] !== null));\n}\n\n/** The first `default` that names something `values` does not offer, for the\n * WRITE path to refuse. Kept out of the parse so a file already carrying one\n * keeps loading — see `fieldDefaultValue`. */\nexport function firstUnknownDefault(schema: CollectionSchema): { key: string; value: string; values: string[] } | null {\n for (const [key, field] of Object.entries(schema.fields)) {\n if (field.type !== \"enum\" || field.default === undefined) continue;\n if (!field.values.includes(field.default)) return { key, value: field.default, values: field.values };\n }\n return null;\n}\n","// Pure `when`-predicate visibility helpers for schema-driven\n// collections — used both for action buttons and for conditionally\n// shown fields. Kept as their own module (no Vue) so CollectionView\n// can stay thin and the match semantics are pinned by unit tests.\n// Domain-free: the host compares the stringified record value against\n// the allowed set with no knowledge of what the field means.\n\nimport { fieldTextOrNull } from \"./fieldText\";\n\n/** A `when` predicate: render only when the open record's `field`\n * (stringified) is one of `in`. Shared shape for action buttons and\n * conditionally visible fields. */\nexport interface WhenPredicate {\n field: string;\n in: string[];\n}\n\n/** Core matcher:\n * - no `when` ⇒ always true (visible);\n * - otherwise true only when `record[when.field]` is present and its\n * stringified value is one of `when.in`.\n * A missing/undefined/null field is treated as \"not a match\"\n * (hidden), so a status-gated target never shows on a record that\n * lacks the status. */\nexport function whenMatches(when: WhenPredicate | undefined, record: Record<string, unknown>): boolean {\n if (!when) return true;\n // `fieldTextOrNull` rather than `String(...)`: an array/object field would\n // stringify to \"[object Object]\" and could match a `when.in` entry by\n // accident. No text ⇒ no match, same as an absent field.\n const text = fieldTextOrNull(record[when.field]);\n if (text === null) return false;\n return when.in.includes(text);\n}\n\n/** Minimal shape this helper needs from an action — the optional state\n * gate, whichever name its kind uses: `when` on the seeded kinds\n * (chat/agent), `require` on mutate. Accepts the full CollectionAction\n * union (each variant declares at most one of the two). */\nexport interface ActionWithWhen {\n when?: WhenPredicate | undefined;\n require?: WhenPredicate | undefined;\n}\n\n/** True when the action's button should render against `record` — and,\n * server-side, whether it may RUN (visibility is the authorization\n * rule, for every kind). */\nexport function actionVisible(action: ActionWithWhen, record: Record<string, unknown>): boolean {\n return whenMatches(action.when ?? action.require, record);\n}\n\n/** The run key naming one in-flight `kind: \"agent\"` action button —\n * written by the server's dispatch guard, read back by the client from\n * the detail response's `runningActions` to drive the spinner. ONE\n * builder (isomorphic) so the two sides can't drift. Collection-level\n * and per-record actions live in distinct namespaces so an id collision\n * between `actions` and `collectionActions` can't alias. */\nexport function agentActionRunKey(actionId: string, itemId?: string): string {\n return itemId === undefined ? `collection/${actionId}` : `item/${itemId}/${actionId}`;\n}\n\n/** Minimal shape this helper needs from a field spec — just its\n * optional `when` predicate. Accepts the full FieldSpec too. */\nexport interface FieldWithWhen {\n when?: WhenPredicate | undefined;\n}\n\n/** True when the field should render against `record`. A field with\n * no `when` is always shown; otherwise it's shown only when the\n * record matches (e.g. hide a rating field until `visited` is true).\n * Purely presentational — a hidden field's stored value is never\n * altered, so toggling the gate back on restores it. */\nexport function fieldVisible(field: FieldWithWhen, record: Record<string, unknown>): boolean {\n return whenMatches(field.when, record);\n}\n","// Pure resolution for `backlinks` fields (plan step ② of\n// plans/done/collection-ontology.md): the display-only reverse side of `ref`.\n// Both the server enrichment (`server/derive.ts`) and the client detail\n// view derive the row set through THESE helpers, so the LLM (getItems)\n// and the user (record panel) always see the same rows — the same\n// single-implementation rule `deriveAll` follows for formulas. No zod,\n// no I/O; safe for the browser barrel.\n\nimport { isRecord, isUnknownArray } from \"@mulmoclaude/common\";\n\nimport { whenMatches } from \"./actionVisible\";\nimport { fieldTextOrNull } from \"./fieldText\";\nimport type { CollectionFieldSpec, CollectionItem } from \"./schema\";\n\n/** The `backlinks` member of the field-spec union. */\nexport type BacklinksFieldSpec = Extract<CollectionFieldSpec, { type: \"backlinks\" }>;\n\n/** Does `item` reference `recordId` through `via`? Three shapes, tried in\n * order so the dotted form never regresses an existing flat key:\n *\n * - Exact key (`via` is an own property of `item`): the field holds the id\n * directly — compared as text, like every ref deref. This is checked FIRST\n * because `schemaZ` accepts any field name (`z.string()`), so a field\n * literally named `\"client.id\"` keeps its pre-nesting flat-match behaviour\n * instead of being reinterpreted as a table path. A field holding an\n * array/object has no id to compare, so it matches nothing (rather than\n * testing \"[object Object]\" against the record id).\n * - Nested ref (`via: \"<tableField>.<refColumn>\"`, split on the FIRST `.`,\n * only when no exact key exists): the source stores its ref one level down,\n * inside a `table` field's rows (the ontology scanner advertises these as\n * `${key}.${subKey}`). Matches when `item[tableField]` is an array and ANY\n * row's `refColumn` derefs to `recordId`. A record referencing the target in\n * several rows still matches once — the caller filters over source records,\n * each yielded at most once.\n * - No dot and no such key: matches nothing.\n *\n * Fail-soft throughout: a dotted `via` whose table field is absent / non-array,\n * or whose rows lack the column, matches nothing — the same \"a `via` that\n * doesn't resolve matches nothing\" contract as the flat case. Deeper nesting\n * (`a.b.c`) makes `b.c` the column name, which no row carries → no match. */\nexport function viaMatches(via: string, item: CollectionItem, recordId: string): boolean {\n if (Object.hasOwn(item, via)) return fieldTextOrNull(item[via]) === recordId;\n const dot = via.indexOf(\".\");\n if (dot === -1) return false;\n const tableField = via.slice(0, dot);\n const refColumn = via.slice(dot + 1);\n const rows = item[tableField];\n if (!isUnknownArray(rows)) return false;\n return rows.some((row) => isRecord(row) && fieldTextOrNull(row[refColumn]) === recordId);\n}\n\n/** The SOURCE records whose `via` field stores `recordId` (compared as\n * strings, like every ref deref), with the optional `filter` applied —\n * in the source items' given order. `via` may be a top-level column or a\n * `<tableField>.<refColumn>` path into a `table` field's rows (see\n * {@link viaMatches}). Fail-soft by construction: a `via` that doesn't\n * resolve on the source records simply matches nothing.\n * Callers pass DERIVED source records, so a `filter`/`display` on a\n * derived column works when its formula is SELF-CONTAINED (an invoice\n * `total` = sum over its own line items); a source column that derefs\n * yet another collection stays absent — the same each-record-derives-\n * against-itself rule ref targets follow. */\nexport function backlinkRows(spec: Pick<BacklinksFieldSpec, \"via\" | \"filter\">, recordId: string, sourceItems: CollectionItem[]): CollectionItem[] {\n if (!recordId) return [];\n return sourceItems.filter((item) => viaMatches(spec.via, item, recordId) && whenMatches(spec.filter, item));\n}\n\n/** Project one backlink row to the keys consumers surface: the source\n * collection's primaryKey (rows must stay addressable — it's the link\n * target) plus the declared `display` columns. Keys the row doesn't\n * carry are simply absent, mirroring `projectFields` in getItems. */\nexport function projectBacklinkRow(row: CollectionItem, display: readonly string[], primaryKey: string): CollectionItem {\n const keys = display.includes(primaryKey) ? display : [primaryKey, ...display];\n // Own-property only (matches `viaMatches` above): a display column named\n // `toString` must be absent, not project an inherited prototype function.\n return Object.fromEntries(keys.filter((key) => Object.hasOwn(row, key)).map((key) => [key, row[key]]));\n}\n\n/** The `rollup` member of the field-spec union. */\nexport type RollupFieldSpec = Extract<CollectionFieldSpec, { type: \"rollup\" }>;\n\n/** Numeric coercion shared by the strict record lint (`./recordZ`) and\n * rollup sums: a plain number, or a non-blank numeric string (renderers\n * coerce those via `Number(...)`, so they display fine). Anything else —\n * arrays (`[]` stringifies to `\"\"` = 0, `[42]` to `\"42\"`), booleans,\n * objects — is NaN. Lives here (zod-free) so both consumers share one\n * definition of \"numeric\". */\nexport function coerceNumeric(value: unknown): number {\n if (typeof value === \"number\") return value;\n if (typeof value === \"string\" && value.trim() !== \"\") return Number(value);\n return NaN;\n}\n\n/** The rollup aggregate over the matching source rows (plan step ⑤):\n * `count` = how many rows match; `sum` = the total of `column` over\n * them, skipping non-numeric / absent values (a partially-filled source\n * still sums what's there). An EMPTY match set is a real 0 — the\n * fail-soft null lives at the caller, for a source collection that\n * couldn't be resolved at all. Same derived-source-records contract as\n * `backlinkRows`: pass records derived against themselves, so summing a\n * self-contained derived column (an invoice `total`) works. */\nexport function rollupValue(spec: Pick<RollupFieldSpec, \"via\" | \"filter\" | \"op\" | \"column\">, recordId: string, sourceItems: CollectionItem[]): number {\n const rows = backlinkRows(spec, recordId, sourceItems);\n if (spec.op === \"count\") return rows.length;\n let total = 0;\n for (const row of rows) {\n const value = coerceNumeric(spec.column === undefined ? undefined : row[spec.column]);\n if (Number.isFinite(value)) total += value;\n }\n return total;\n}\n","// The single implementation of \"which linked collections must be\n// preloaded\" for a schema — walked identically by the server (derive /\n// enrich, `server/derive.ts`) and the client (linked-cache fetch,\n// collection-plugin). Both sides MUST agree: the server derefs exactly the\n// collections the client caches, so a new ref-shaped field type is taught\n// here once instead of drifting between two mirrored copies. Pure schema\n// walk, no zod / I/O — safe for the browser barrel.\n\nimport type { CollectionFieldSpec, CollectionSchema } from \"./schema\";\n\n/** Slugs of every collection referenced by a `ref` field — top-level and\n * one level into `table` sub-fields (nested tables are schema-rejected,\n * so a single recursion suffices). */\nexport function uniqueRefTargets(schema: CollectionSchema): string[] {\n const targets = new Set<string>();\n const walk = (fields: Record<string, CollectionFieldSpec>): void => {\n for (const field of Object.values(fields)) {\n if (field.type === \"ref\" && typeof field.to === \"string\" && field.to.length > 0) targets.add(field.to);\n if (field.type === \"table\" && field.of) walk(field.of);\n }\n };\n walk(schema.fields);\n return [...targets];\n}\n\n/** Slugs of every collection referenced by an `embed` field. Top-level\n * only — the schema rejects `embed` inside a table's `of`, so no\n * recursion. */\nexport function uniqueEmbedTargets(schema: CollectionSchema): string[] {\n const targets = new Set<string>();\n for (const field of Object.values(schema.fields)) {\n if (field.type === \"embed\" && typeof field.to === \"string\" && field.to.length > 0) targets.add(field.to);\n }\n return [...targets];\n}\n\n/** Slugs of every SOURCE collection a `backlinks` or `rollup` field\n * reverses over — loaded once (the two field kinds share one load).\n * Top-level only, like `embed` (the schema rejects both inside a table's\n * `of`). */\nexport function uniqueBacklinkSources(schema: CollectionSchema): string[] {\n const sources = new Set<string>();\n for (const field of Object.values(schema.fields)) {\n if ((field.type === \"backlinks\" || field.type === \"rollup\") && field.from.length > 0) sources.add(field.from);\n }\n return [...sources];\n}\n","// Pure SQL-like `where` predicate for `dynamicIcon` (see\n// `DynamicIconSource.where` / `DynamicIconRule.where` in `./schema`).\n// An AND of typed conditions — richer than the single-field `CollectionWhen`\n// used elsewhere (fields/actions via `./actionVisible`), which stays as-is\n// for its existing callers. No fs, no host state. The condition SHAPES are\n// derived from the zod source of truth in `./schemaZ` (type-only imports —\n// this evaluator stays zod-free at runtime).\n\nimport type { z } from \"zod\";\nimport type { ValueRefZ, WhereCondZ, WhereZ } from \"./schemaZ\";\n\n/** Reads the comparison value from a field instead of a schema literal:\n * - `record` set → another record: `recordsById[record][field]` (e.g. a\n * `_config` singleton's `defaultCity`, following a per-user setting);\n * - `record` omitted → the SAME record being matched (field-to-field, e.g.\n * `spent > budget`). */\nexport type ValueRef = z.infer<typeof ValueRefZ>;\n\n/** One typed condition: `record[field] <op> value`. The comparison value is\n * either a literal `value` (a plain string for every op except `in`, which\n * takes the allowed set) or a `valueFrom` reference resolved against the\n * `recordsById` map passed to `matchesWhere`. Exactly one of the two is\n * expected — enforced by zod at the schema boundary (`./schemaZ`), not\n * here. */\nexport type WhereCond = z.infer<typeof WhereCondZ>;\n\n/** Comparison operators one `WhereCond` may apply to `record[field]`. */\nexport type WhereOp = WhereCond[\"op\"];\n\n/** A `where` clause is the AND of its conditions — every one must match. */\nexport type Where = z.infer<typeof WhereZ>;\n\n/** True when `record[field]` is absent (`undefined`/`null`) — the only case\n * where `ne` and every other op disagree on the result. */\nfunction isMissing(raw: unknown): boolean {\n return raw === undefined || raw === null;\n}\n\n/** Own-property read for a user/LLM-controlled key (`cond.field`,\n * `valueFrom.record`, `valueFrom.field`). A bare `obj[key]` reaches inherited\n * Object.prototype members, so `field: \"toString\"` would read a function\n * instead of an absent value (spurious match), and `record: \"constructor\"`\n * would read the `Object` function whose `.name`/`.length` are plausible\n * bogus comparands — breaking the \"unresolved ⇒ never matches\" contract\n * (#2323). A prototype key resolves to `undefined` (absent). */\nfunction ownProp<T>(obj: Record<string, T>, key: string): T | undefined {\n return Object.hasOwn(obj, key) ? obj[key] : undefined;\n}\n\n/** The effective comparison value for `cond`: its literal `value`, or — for\n * a `valueFrom` reference — the target field read out of `recordsById`.\n * `undefined` means UNRESOLVED (no such record, or the field on it is\n * missing); the caller must treat that as \"never matches\", not as a\n * literal `undefined` value to compare against. */\nfunction resolveValue(cond: WhereCond, record: Record<string, unknown>, recordsById: Record<string, Record<string, unknown>>): string | string[] | undefined {\n if (!cond.valueFrom) return cond.value;\n const { record: refRecord, field } = cond.valueFrom;\n const target = refRecord === undefined ? record : ownProp(recordsById, refRecord);\n const raw = target === undefined ? undefined : ownProp(target, field);\n return isMissing(raw) ? undefined : String(raw);\n}\n\nfunction matchesNumericOp(operator: \"gt\" | \"gte\" | \"lt\" | \"lte\", left: number, right: number): boolean {\n if (operator === \"gt\") return left > right;\n if (operator === \"gte\") return left >= right;\n if (operator === \"lt\") return left < right;\n return left <= right;\n}\n\n/** `Number(\"\")` / `Number(\" \")` are `0`, not `NaN`, so treat a blank string\n * as non-numeric explicitly — an empty field must fail a numeric compare,\n * not read as zero. */\nfunction toNumber(raw: string): number {\n return raw.trim() === \"\" ? NaN : Number(raw);\n}\n\nfunction matchesNumeric(operator: \"gt\" | \"gte\" | \"lt\" | \"lte\", raw: string, value: string | string[]): boolean {\n if (Array.isArray(value)) return false;\n const left = toNumber(raw);\n const right = toNumber(value);\n if (Number.isNaN(left) || Number.isNaN(right)) return false;\n return matchesNumericOp(operator, left, right);\n}\n\n/** True when the present string `raw` satisfies `operator` against the\n * resolved `value` (field known to exist — MISSING is handled by the\n * caller before this runs, and an UNRESOLVED `valueFrom` never reaches\n * here either). */\nfunction matchesPresent(operator: WhereOp, raw: string, value: string | string[]): boolean {\n switch (operator) {\n case \"eq\":\n return raw === String(value);\n case \"ne\":\n return raw !== String(value);\n case \"in\":\n return Array.isArray(value) && value.includes(raw);\n case \"contains\":\n return raw.includes(String(value));\n case \"gt\":\n case \"gte\":\n case \"lt\":\n case \"lte\":\n return matchesNumeric(operator, raw, value);\n default:\n return false;\n }\n}\n\n/** True when `record` satisfies one condition, given `recordsById` to\n * resolve a `valueFrom` reference. Two independent MISSING cases, checked\n * in order:\n * - `record[cond.field]` absent (`undefined`/`null`) → matches only `ne`\n * (vacuously true — \"not equal to X\" holds when there's no value at\n * all); every other op is false. Unchanged from the literal-`value`\n * behaviour, regardless of whether `valueFrom` would also resolve.\n * - the resolved comparison value is UNRESOLVED (a `valueFrom` whose\n * target record/field doesn't exist) → false for EVERY op, including\n * `ne` — a broken reference must never spuriously match. */\nfunction matchesCond(cond: WhereCond, record: Record<string, unknown>, recordsById: Record<string, Record<string, unknown>>): boolean {\n const raw = ownProp(record, cond.field);\n if (isMissing(raw)) return cond.op === \"ne\";\n const value = resolveValue(cond, record, recordsById);\n if (value === undefined) return false;\n return matchesPresent(cond.op, String(raw), value);\n}\n\n/** True when `record` satisfies every condition in `where` (AND). An empty\n * `where` matches everything. `recordsById` — the source collection's\n * records keyed by primaryKey — resolves any `valueFrom` reference;\n * omitted (default `{}`) for callers with no cross-record lookups, in\n * which case every `valueFrom` condition is UNRESOLVED and so never\n * matches. */\nexport function matchesWhere(where: Where, record: Record<string, unknown>, recordsById: Record<string, Record<string, unknown>> = {}): boolean {\n return where.every((cond) => matchesCond(cond, record, recordsById));\n}\n","// The \"is this record done?\" predicate — THE single implementation,\n// shared by the notification reconciler (bell clearing, collection-watchers),\n// spawn (successor-predicate fallback, ../server/spawn.ts), and view-side\n// completion filters. Zod-free and I/O-free like the rest of `core/`, so\n// it is browser-safe through the collection barrel.\n//\n// Two completion forms (see `CollectionSchemaZ`'s completion refine):\n// - legacy pair: `completionField` names a stored field and\n// `completionDoneValues` lists the values that mean done —\n// done ⇔ `String(item[completionField])` ∈ `completionDoneValues`.\n// - flag form: `completionField` names a `flag` field (and\n// `completionDoneValues` is absent) — done ⇔ the flag's `where`\n// matches. Evaluated directly against the raw record here (NOT read\n// from a materialized value) because callers like the reconciler and\n// spawn work on records straight off disk, before any `deriveAll`\n// enrichment. That raw evaluation is CORRECT BY CONSTRUCTION: a\n// schema-level refine rejects a completion flag whose `where`\n// references computed fields, so every condition reads stored data.\n\nimport { fieldText, fieldTextOrNull } from \"./fieldText\";\nimport { matchesWhere, type Where } from \"./where\";\nimport type { CollectionFieldSpec, CollectionItem } from \"./schema\";\n\n/** The slice of a parsed schema the done predicate reads — minimal\n * structural shape (like `DerivableSchema`) so the client and server\n * `CollectionSchema` types both satisfy it as-is. */\nexport interface CompletionSchemaView {\n /** Optional so legacy-pair callers (and their test fixtures) that\n * never consult field specs keep working; only the flag form needs\n * to look the completion field up. */\n fields?: Record<string, { type: string; where?: Where }> | undefined;\n completionField?: string | undefined;\n completionDoneValues?: readonly string[] | undefined;\n}\n\n/** True iff the schema declares completion tracking AND `item` is done\n * under whichever completion form the schema uses (see module doc). */\nexport function itemIsDone(schema: CompletionSchemaView, item: Record<string, unknown>): boolean {\n const { completionField, completionDoneValues } = schema;\n if (!completionField) return false;\n const spec = schema.fields?.[completionField];\n if (spec?.type === \"flag\" && spec.where) return matchesWhere(spec.where, item);\n if (!completionDoneValues) return false;\n // An array/object field has no text form; treat it as \"not done\" rather than\n // letting \"[object Object]\" match a configured done-value.\n const text = fieldTextOrNull(item[completionField]);\n if (text === null) return false;\n return completionDoneValues.includes(text);\n}\n\n/** The schema slice `completionCoveredByFieldChip` reads: field kinds (it\n * inspects `boolean` / `toggle` variants' `field` / `onValue`) plus the\n * completion pair. Minimal structural shape so both the client and server\n * `CollectionSchema` types satisfy it as-is. */\nexport interface CompletionChipSchemaView {\n fields: Record<string, { type: string; field?: string; onValue?: string }>;\n completionField?: string | undefined;\n completionDoneValues?: readonly string[] | undefined;\n}\n\n/** True when an existing FIELD chip already expresses the legacy completion\n * predicate exactly, so a synthesized \"done\" chip would be a duplicate: a\n * boolean `completionField` (done ⇔ `\"true\"` ⇔ the boolean's own chip), or a\n * `toggle` projecting the `completionField` whose `onValue` is the single\n * done value (the todos-schema shape: toggle \"Done\" on `status` +\n * `completionDoneValues: [\"done\"]`). A superset pair (extra done values)\n * still synthesizes — no field chip covers it. */\nexport function completionCoveredByFieldChip(schema: CompletionChipSchemaView): boolean {\n const { completionField, completionDoneValues } = schema;\n if (completionDoneValues?.length !== 1) return false;\n const [doneValue] = completionDoneValues;\n if (schema.fields[completionField ?? \"\"]?.type === \"boolean\") return doneValue === \"true\";\n return Object.values(schema.fields).some((field) => field.type === \"toggle\" && field.field === completionField && field.onValue === doneValue);\n}\n\n/** Whether a `toggle` field reads as checked: its projected enum field currently\n * equals `onValue`. The toggle stores nothing of its own, so this reads the raw\n * projected value; a non-toggle field is never checked. */\nexport function toggleChecked(item: CollectionItem, field: CollectionFieldSpec): boolean {\n return field.type === \"toggle\" && fieldText(item[field.field]) === field.onValue;\n}\n\n/** A `flag` FIELD's boolean for one row, read off the already-enriched record\n * (so a flag computed from derived/rollup inputs is correct). Strict `=== true`\n * on purpose: a truthy non-boolean (\"yes\", 1) is NOT a set flag, so a stray\n * value can never render as an active flag. */\nexport function flagFieldValue(record: Record<string, unknown>, key: string): boolean {\n return record[key] === true;\n}\n\n/** One entry in the table's flag-filter menu: a real `flag` / `boolean` /\n * `toggle` field, or the synthesized legacy-completion chip (`synthetic`,\n * predicate = `itemIsDone`). */\nexport interface FlagChip {\n key: string;\n label: string;\n synthetic?: boolean;\n}\n\n/** The schema slice `chipMatches` reads: full field specs (it inspects the\n * `toggle` variant's `field` / `onValue`) plus the completion pair that\n * `itemIsDone` needs for the synthesized chip. */\nexport interface ChipMatchSchema {\n fields: Record<string, CollectionFieldSpec>;\n completionField?: string | undefined;\n completionDoneValues?: readonly string[] | undefined;\n}\n\n/** Whether one row satisfies a chip's predicate: `itemIsDone` for the\n * synthesized completion chip, the projected value for a `toggle`, the stored\n * boolean for a `boolean`, else the computed flag value. The flag branch reads\n * the ENRICHED record via the injected `deriveRecord` (kept a parameter so this\n * stays pure and framework-free). */\nexport function chipMatches(\n chip: FlagChip,\n schema: ChipMatchSchema,\n item: CollectionItem,\n deriveRecord: (item: CollectionItem) => Record<string, unknown>,\n): boolean {\n if (chip.synthetic) return itemIsDone(schema, item);\n const field = schema.fields[chip.key];\n if (field?.type === \"toggle\") return toggleChecked(item, field);\n if (field?.type === \"boolean\") return item[chip.key] === true;\n return flagFieldValue(deriveRecord(item), chip.key);\n}\n","// Pure resolver for a collection's dynamic launcher-shortcut icon (see\n// `CollectionSchema.dynamicIcon`). Selects one \"source\" record from a\n// (possibly cross-collection, optionally `where`-filtered) records pool,\n// then maps it through a first-match-wins rules list to an icon name.\n// No fs, no host state — the server-side compute\n// (`packages/core/src/collection/server/dynamicIcon.ts`) loads the source\n// collection's records and calls these.\n\nimport { fieldText } from \"./fieldText\";\nimport { matchesWhere } from \"./where\";\nimport type { CollectionFieldSpec, CollectionItem, CollectionSchema, DynamicIconSource, DynamicIconSpec } from \"./schema\";\n\n/** The record with the greatest `String(record[field])` (localeCompare) —\n * ties keep the first-seen record (stable left-to-right `reduce`). */\n// Seedless `reduce` is safe here: the sole caller (selectDynamicRecord) returns\n// null on an empty pool before this line, so an empty pool never arrives.\nfunction latestByField(pool: CollectionItem[], field: string): CollectionItem {\n return pool.reduce((latest, candidate) => (fieldText(candidate[field]).localeCompare(fieldText(latest[field])) > 0 ? candidate : latest));\n}\n\n/** Reduce `records` to the one record that decides the effective icon, per\n * `source`'s `where` filter + `from` strategy:\n * - pool = `source.where`-filtered records, or every record when unset;\n * - an empty pool resolves to `null` (no source record → fallback);\n * - `from: \"first\"` / `\"when\"` → the first pool record (storage order);\n * - `from: \"latest\"` (default), with `orderBy` given → the pool record\n * whose `String(record[orderBy])` sorts highest;\n * - `from: \"latest\"`, with no `orderBy` → the last pool record.\n * `recordsById` (the source collection's records keyed by primaryKey)\n * resolves any `valueFrom` reference inside `source.where`; omitted for\n * callers with no cross-record lookups. */\nexport function selectDynamicRecord(\n records: CollectionItem[],\n source: DynamicIconSource,\n orderBy: string | undefined,\n recordsById: Record<string, CollectionItem> = {},\n): CollectionItem | null {\n const { where } = source;\n const pool = where ? records.filter((record) => matchesWhere(where, record, recordsById)) : records;\n const [first] = pool;\n if (!first) return null;\n if (source.from === \"first\" || source.from === \"when\") return first;\n return orderBy ? latestByField(pool, orderBy) : (pool[pool.length - 1] ?? null);\n}\n\n/** Map a resolved source record to the effective icon: `spec.fallback`\n * (or the collection's own static `icon`) when there's no record or no\n * rule matches; otherwise the `icon` of the first rule whose `where`\n * matches the record. `recordsById` resolves any `valueFrom` reference\n * inside a rule's `where`, same as `selectDynamicRecord`. */\nexport function resolveIcon(\n record: CollectionItem | null,\n spec: DynamicIconSpec,\n staticIcon: string,\n recordsById: Record<string, CollectionItem> = {},\n): string {\n const fallback = spec.fallback ?? staticIcon;\n if (!record) return fallback;\n const matched = spec.rules.find((rule) => matchesWhere(rule.where, record, recordsById));\n return matched ? matched.icon : fallback;\n}\n\nconst isDateLikeField = (field: CollectionFieldSpec): boolean => field.type === \"date\" || field.type === \"datetime\";\n\n/** The first field key (declaration order) whose type is `date` or\n * `datetime` — the default `orderBy` for `from: \"latest\"` when a\n * `DynamicIconSource` doesn't name one. `undefined` when the schema has\n * no date-like field. */\nexport function firstDateField(schema: CollectionSchema): string | undefined {\n return Object.entries(schema.fields).find(([, field]) => isDateLikeField(field))?.[0];\n}\n","// Tiny expression evaluator for the `derived` field type on\n// schema-driven collections (see plans/done/feat-mc-invoice.md).\n//\n// Grammar (recursive-descent, no precedence climbing — six\n// non-terminals total):\n//\n// expr := term (('+' | '-') term)*\n// term := factor (('*' | '/') factor)*\n// factor := number | sumCall | refAccess | identifier | '(' expr ')'\n// sumCall:= 'sum' '(' sumArg ')'\n// sumArg := tableCol (('*' | '/') tableCol)* // e.g. lineItems[].quantity * lineItems[].rate\n// tableCol := identifier '[]' '.' identifier\n// refAccess := identifier '.' identifier // e.g. ticker.price — deref a ref field into its target record\n//\n// `identifier` accepts top-level field names (single segment).\n// Inside `sumArg`, identifiers are the `<table>[].col` form.\n// A two-segment `<field>.<col>` at factor level is a *ref deref*:\n// `<field>` must be a `ref`-typed field on this record (its stored\n// value is the target item's slug), and `<col>` is a numeric column\n// read from that target record. The caller resolves the target into\n// `ctx.refs` (it owns the schema + the loaded target collection);\n// the evaluator stays pure and never does I/O.\n//\n// What's deliberately NOT supported (and would parse-error rather\n// than silently misbehave):\n// - String literals, boolean operators, comparisons, conditionals\n// - Nested function calls beyond `sum(...)`\n// - Anything in the record that isn't a number / table-of-objects\n//\n// All evaluation is pure — no eval(), no Function constructor.\n// Returns `null` on any failure (parse error, unbound identifier,\n// non-finite arithmetic). The caller renders `null` as em-dash in\n// the table cell + form display.\n\nimport { isObj, isRecord, isUnknownArray } from \"@mulmoclaude/common\";\n\nexport interface FormulaContext {\n /** The record being evaluated. For derived fields in the form,\n * this is the live draft (text + table both converted via the\n * same `draftToRecord` pipeline). For the main table cell,\n * this is the persisted item. */\n record: Record<string, unknown>;\n /** Resolved ref-target records for THIS row, keyed by the local\n * `ref` field name. The caller (which has the schema + the linked\n * collection's items loaded) maps each ref field's stored slug to\n * the full target record and passes it here, so a `<field>.<col>`\n * formula can read a numeric column off the referenced record\n * (e.g. `shares * ticker.price`). A missing key or `null` value\n * (unknown field / dangling slug) makes that deref evaluate to\n * NaN → the whole formula returns `null` → em-dash, consistent\n * with every other failure mode. Absent ⇒ no refs available. */\n refs?: Record<string, Record<string, unknown> | null>;\n}\n\nexport function evaluateDerived(formula: string, ctx: FormulaContext): number | null {\n let tokens: Token[];\n try {\n tokens = tokenize(formula);\n } catch {\n return null;\n }\n // eslint-disable-next-line @typescript-eslint/no-use-before-define -- Parser class is defined later in the file (grouped with its AST + evaluator); evaluateDerived runs after module init so the TDZ concern doesn't apply.\n const parser = new Parser(tokens);\n let ast: Node;\n try {\n ast = parser.parseExpr();\n if (!parser.atEnd()) return null; // trailing junk\n } catch {\n return null;\n }\n const value = evaluate(ast, ctx);\n return Number.isFinite(value) ? value : null;\n}\n\n// ─── Tokens ────────────────────────────────────────────────\n\ntype PunctKind = \"(\" | \")\" | \"+\" | \"-\" | \"*\" | \"/\" | \"[]\" | \".\";\ntype TokenKind = \"number\" | \"ident\" | PunctKind;\n\n// Discriminated so a `number` token's payload is a number and an\n// `ident` token's payload is a string, with no reader having to assert it.\ntype Token = { kind: \"number\"; value: number } | { kind: \"ident\"; value: string } | { kind: PunctKind };\n\nconst SINGLE_CHAR_PUNCT: readonly PunctKind[] = [\"(\", \")\", \"+\", \"-\", \"*\", \"/\", \".\"];\n\nfunction isSingleCharPunct(char: string): char is PunctKind {\n return SINGLE_CHAR_PUNCT.some((punct) => punct === char);\n}\n\ninterface Cursor {\n input: string;\n index: number;\n}\n\nfunction consumeWhitespace(cur: Cursor): boolean {\n const char = cur.input[cur.index];\n if (char === \" \" || char === \"\\t\" || char === \"\\n\") {\n cur.index++;\n return true;\n }\n return false;\n}\n\nfunction consumeNumber(cur: Cursor): Token | null {\n const char = cur.input[cur.index] ?? \"\";\n const next = cur.input[cur.index + 1] ?? \"\";\n if (!isDigit(char) && !(char === \".\" && isDigit(next))) return null;\n let raw = \"\";\n while (cur.index < cur.input.length) {\n const here = cur.input[cur.index] ?? \"\";\n if (!isDigit(here) && here !== \".\") break;\n raw += here;\n cur.index++;\n }\n const num = Number(raw);\n if (!Number.isFinite(num)) throw new Error(\"bad number\");\n return { kind: \"number\", value: num };\n}\n\nfunction consumeIdent(cur: Cursor): Token | null {\n const char = cur.input[cur.index] ?? \"\";\n if (!isIdentStart(char)) return null;\n let raw = \"\";\n while (cur.index < cur.input.length && isIdentChar(cur.input[cur.index] ?? \"\")) {\n raw += cur.input[cur.index];\n cur.index++;\n }\n return { kind: \"ident\", value: raw };\n}\n\nfunction consumePunct(cur: Cursor): Token | null {\n const char = cur.input[cur.index] ?? \"\";\n if (char === \"[\" && cur.input[cur.index + 1] === \"]\") {\n cur.index += 2;\n return { kind: \"[]\" };\n }\n if (isSingleCharPunct(char)) {\n cur.index++;\n return { kind: char };\n }\n return null;\n}\n\nfunction tokenize(input: string): Token[] {\n const tokens: Token[] = [];\n const cur: Cursor = { input, index: 0 };\n while (cur.index < input.length) {\n if (consumeWhitespace(cur)) continue;\n // Number FIRST so a leading-dot literal (`.25`) isn't split by\n // the `.` punctuation branch.\n const numTok = consumeNumber(cur);\n if (numTok) {\n tokens.push(numTok);\n continue;\n }\n const punctTok = consumePunct(cur);\n if (punctTok) {\n tokens.push(punctTok);\n continue;\n }\n const identTok = consumeIdent(cur);\n if (identTok) {\n tokens.push(identTok);\n continue;\n }\n throw new Error(`unexpected char ${input[cur.index]}`);\n }\n return tokens;\n}\n\nfunction isDigit(char: string): boolean {\n return char >= \"0\" && char <= \"9\";\n}\nfunction isIdentStart(char: string): boolean {\n return (char >= \"a\" && char <= \"z\") || (char >= \"A\" && char <= \"Z\") || char === \"_\";\n}\nfunction isIdentChar(char: string): boolean {\n return isIdentStart(char) || isDigit(char);\n}\n\n// ─── AST + Parser ───────────────────────────────────────────\n\ntype AdditiveOperator = \"+\" | \"-\";\ntype MultiplicativeOperator = \"*\" | \"/\";\ntype BinaryOperator = AdditiveOperator | MultiplicativeOperator;\n\ntype Node =\n | { kind: \"num\"; value: number }\n | { kind: \"ident\"; name: string }\n | { kind: \"ref\"; field: string; col: string }\n | { kind: \"binop\"; operator: BinaryOperator; left: Node; right: Node }\n | { kind: \"sum\"; arg: SumArg };\n\nfunction matchAdditive(kind: TokenKind | undefined): AdditiveOperator | null {\n return kind === \"+\" || kind === \"-\" ? kind : null;\n}\n\nfunction matchMultiplicative(kind: TokenKind | undefined): MultiplicativeOperator | null {\n return kind === \"*\" || kind === \"/\" ? kind : null;\n}\n\ninterface TableCol {\n table: string;\n col: string;\n}\n\ninterface SumArg {\n // factors multiplied/divided together; each is a (tableName, colName) ref into a row.\n factors: TableCol[];\n /** Operators between factors: length = factors.length - 1; each\n * is \"*\" or \"/\". For a single-factor sum (`sum(lineItems[].amount)`)\n * this is empty. */\n operators: MultiplicativeOperator[];\n}\n\nclass Parser {\n private cursor = 0;\n constructor(private readonly tokens: Token[]) {}\n\n atEnd(): boolean {\n return this.cursor >= this.tokens.length;\n }\n private peek(): Token | undefined {\n return this.tokens[this.cursor];\n }\n private consume(): Token {\n const tok = this.tokens[this.cursor++];\n if (!tok) throw new Error(\"unexpected end of input\");\n return tok;\n }\n private expectPunct(kind: PunctKind): void {\n const tok = this.consume();\n if (tok.kind !== kind) throw new Error(`expected ${kind}, got ${tok.kind}`);\n }\n private expectIdent(): string {\n const tok = this.consume();\n if (tok.kind !== \"ident\") throw new Error(`expected ident, got ${tok.kind}`);\n return tok.value;\n }\n private takeOperator<Op extends BinaryOperator>(match: (kind: TokenKind | undefined) => Op | null): Op | null {\n const operator = match(this.peek()?.kind);\n if (operator) this.consume();\n return operator;\n }\n /** Yields each operator of a chain, consuming it. Iteration keeps the caller's\n * stack depth constant — a formula is user input, so its length must not\n * decide whether we overflow. */\n private *operatorRun<Op extends BinaryOperator>(match: (kind: TokenKind | undefined) => Op | null): Generator<Op> {\n while (true) {\n const operator = this.takeOperator(match);\n if (!operator) return;\n yield operator;\n }\n }\n\n private parseChain<Op extends BinaryOperator>(match: (kind: TokenKind | undefined) => Op | null, parseOperand: () => Node): Node {\n const left = parseOperand();\n const rest: { operator: Op; right: Node }[] = [];\n for (const operator of this.operatorRun(match)) {\n rest.push({ operator, right: parseOperand() });\n }\n return rest.reduce<Node>((accumulated, { operator, right }) => ({ kind: \"binop\", operator, left: accumulated, right }), left);\n }\n\n parseExpr(): Node {\n return this.parseChain(matchAdditive, () => this.parseTerm());\n }\n\n private parseTerm(): Node {\n return this.parseChain(matchMultiplicative, () => this.parseFactor());\n }\n\n private parseFactor(): Node {\n const tok = this.peek();\n if (!tok) throw new Error(\"unexpected end in factor\");\n if (tok.kind === \"number\") {\n this.consume();\n return { kind: \"num\", value: tok.value };\n }\n if (tok.kind === \"(\") {\n this.consume();\n const inner = this.parseExpr();\n this.expectPunct(\")\");\n return inner;\n }\n if (tok.kind === \"ident\") return this.parseIdentFactor(tok.value);\n throw new Error(`unexpected token ${tok.kind} in factor`);\n }\n\n private parseIdentFactor(name: string): Node {\n // sum(...) — only function call we support\n if (name === \"sum\" && this.tokens[this.cursor + 1]?.kind === \"(\") {\n this.consume(); // ident\n this.expectPunct(\"(\");\n const arg = this.parseSumArg();\n this.expectPunct(\")\");\n return { kind: \"sum\", arg };\n }\n this.consume(); // ident\n // ref deref: `<field>.<col>` (e.g. ticker.price). The table-row\n // form `<table>[].col` only appears inside sum(), so a `.`\n // immediately after a top-level ident is unambiguously a ref\n // dereference here.\n if (this.peek()?.kind === \".\") {\n this.consume(); // '.'\n return { kind: \"ref\", field: name, col: this.expectIdent() };\n }\n return { kind: \"ident\", name };\n }\n\n private parseSumArg(): SumArg {\n const factors = [this.parseTableCol()];\n const operators: MultiplicativeOperator[] = [];\n for (const operator of this.operatorRun(matchMultiplicative)) {\n operators.push(operator);\n factors.push(this.parseTableCol());\n }\n return { factors, operators };\n }\n\n private parseTableCol(): TableCol {\n const table = this.expectIdent();\n this.expectPunct(\"[]\");\n this.expectPunct(\".\");\n return { table, col: this.expectIdent() };\n }\n}\n\n// ─── Evaluator ──────────────────────────────────────────────\n\ntype LeafNode = Exclude<Node, { kind: \"binop\" }>;\n\n/** Post-order traversal step: either a subtree still to visit, or the operator\n * whose two operands are already on the value stack. */\ntype EvalStep = { kind: \"visit\"; node: Node } | { kind: \"apply\"; operator: BinaryOperator };\n\n/** Walks the tree with an explicit stack. `a + b + c + …` folds\n * left-associatively, so the AST's left spine is as deep as the formula is\n * long — recursing here would let a long (but valid) user-written formula\n * blow the call stack, and the RangeError would escape `evaluateDerived`'s\n * null contract into the caller. */\nfunction evaluate(root: Node, ctx: FormulaContext): number {\n const steps: EvalStep[] = [{ kind: \"visit\", node: root }];\n const values: number[] = [];\n for (let step = steps.pop(); step; step = steps.pop()) {\n if (step.kind === \"apply\") {\n values.push(applyPending(step.operator, values));\n } else if (step.node.kind === \"binop\") {\n const { operator, left, right } = step.node;\n // Popped in reverse, so the left operand lands on `values` first.\n steps.push({ kind: \"apply\", operator }, { kind: \"visit\", node: right }, { kind: \"visit\", node: left });\n } else {\n values.push(evaluateLeaf(step.node, ctx));\n }\n }\n const result = values.pop();\n return result !== undefined && values.length === 0 ? result : Number.NaN;\n}\n\nfunction applyPending(operator: BinaryOperator, values: number[]): number {\n const right = values.pop();\n const left = values.pop();\n // Both operands are pushed before their apply step, so a gap means a\n // malformed traversal — fail soft to NaN like every other bad value.\n if (left === undefined || right === undefined) return Number.NaN;\n return applyBinop(operator, left, right);\n}\n\nfunction evaluateLeaf(node: LeafNode, ctx: FormulaContext): number {\n if (node.kind === \"num\") return node.value;\n if (node.kind === \"ident\") return toFiniteNumber(ctx.record[node.name]);\n if (node.kind === \"ref\") {\n // `<field>.<col>`: read `col` off the resolved target record the\n // caller put in ctx.refs. Unknown field / dangling slug → null →\n // NaN, so the whole formula fails soft to an em-dash.\n const target = ctx.refs?.[node.field] ?? null;\n if (!target) return Number.NaN;\n return toFiniteNumber(target[node.col]);\n }\n return evaluateSum(node.arg, ctx);\n}\n\nfunction applyBinop(operator: BinaryOperator, left: number, right: number): number {\n if (!Number.isFinite(left) || !Number.isFinite(right)) return Number.NaN;\n if (operator === \"+\") return left + right;\n if (operator === \"-\") return left - right;\n if (operator === \"*\") return left * right;\n // operator === \"/\"\n if (right === 0) return Number.NaN;\n return left / right;\n}\n\nfunction evaluateSum(arg: SumArg, ctx: FormulaContext): number {\n const first = arg.factors.at(0);\n if (!first) return 0;\n // All factors must reference the SAME table (you can't multiply\n // a row from lineItems against a row from another table — the\n // semantics would be ambiguous). Reject mismatch.\n if (arg.factors.some((factor) => factor.table !== first.table)) return Number.NaN;\n const rows = ctx.record[first.table];\n if (!isUnknownArray(rows)) return 0;\n // A NaN from any row poisons the total, which the caller turns into null.\n return rows.filter(isObj).reduce((total, row) => total + rowProduct(row, arg), 0);\n}\n\nfunction rowProduct(row: object, { factors, operators }: SumArg): number {\n const [first, ...rest] = factors;\n if (!first) return Number.NaN;\n return rest.reduce(\n (product, factor, index) => {\n // The parser pushes one operator per extra factor, so a gap here means a\n // malformed SumArg — fail soft to NaN like every other bad value.\n const operator = operators[index];\n return operator ? applyBinop(operator, product, columnNumber(row, factor.col)) : Number.NaN;\n },\n columnNumber(row, first.col),\n );\n}\n\nfunction columnNumber(row: object, col: string): number {\n // An array row has no named column, so it fails soft like any other bad value.\n return isRecord(row) ? toFiniteNumber(row[col]) : Number.NaN;\n}\n\nfunction toFiniteNumber(value: unknown): number {\n if (typeof value === \"number\") return Number.isFinite(value) ? value : Number.NaN;\n if (typeof value === \"string\" && value.length > 0) {\n const num = Number(value);\n return Number.isFinite(num) ? num : Number.NaN;\n }\n return Number.NaN;\n}\n","// Own-property read for a plain object indexed by a user/LLM/feed-controlled\n// key. Extracted so the ref/embed resolution in `deriveAll` and `derive`\n// share one guard (where.ts keeps its own private copy for the dynamicIcon\n// `where` evaluator — #2443).\n\n/** Read `obj[key]` only when it is an OWN property. A bare `obj[key]` reaches\n * inherited Object.prototype members, so a dangling ref/embed id like\n * `\"constructor\"` or `\"__proto__\"` resolves to a prototype value (the\n * `Object` function) instead of being absent — breaking the \"missing target\n * ⇒ null (em-dash)\" fail-soft contract and putting a non-serializable\n * function into the API response, where `JSON.stringify` drops the key\n * entirely (#2322). A prototype key resolves to `undefined`; a record whose\n * id is legitimately `\"constructor\"` (an own key) still resolves. */\nexport function ownProp<T>(obj: Record<string, T>, key: string): T | undefined {\n return Object.hasOwn(obj, key) ? obj[key] : undefined;\n}\n","// The derived-field saturation loop for schema-driven collections,\n// extracted from `composables/collections/useCollectionRendering.ts` so\n// the server (manageCollection getItems enrichment) and the client\n// (table cells, form display) evaluate formulas through ONE\n// implementation — if the two ever diverged, the UI and the LLM would\n// disagree on a number. Pure module: no Vue, no I/O.\n//\n// Like `actionVisible.ts`, the input types are minimal structural\n// shapes so both the client `FieldSpec`/`CollectionSchema`\n// (src/components/collectionTypes.ts) and the server\n// `CollectionFieldSpec`/`CollectionSchema`\n// (server/workspace/collections/types.ts) satisfy them as-is.\n\nimport { evaluateDerived, type FormulaContext } from \"./derivedFormula\";\nimport { ownProp } from \"./ownProp\";\nimport { matchesWhere, type Where } from \"./where\";\n\n/** Minimal field shape the derive loop needs — accepts both the client\n * FieldSpec and the server CollectionFieldSpec. */\nexport interface DerivableFieldSpec {\n type: string;\n /** When type === \"ref\": slug of the target collection. */\n to?: string;\n /** When type === \"derived\": formula evaluated against the record. */\n formula?: string;\n /** When type === \"flag\": predicate matched against the record. */\n where?: Where;\n}\n\n/** Minimal schema shape: just the ordered field map. */\nexport interface DerivableSchema {\n fields: Record<string, DerivableFieldSpec>;\n}\n\nexport type DerivableRecord = Record<string, unknown>;\n\n/** Per-target-collection cache of loaded referenced records:\n * target collection slug → item slug → full record. Mirrors the\n * client's `RefRecordCache` / the server's enrichment loader. */\nexport type DeriveRefRecords = Record<string, Record<string, DerivableRecord>>;\n\n/** Map each `ref` field's stored slug to its loaded target record (or\n * null when dangling / not loaded), keyed by the LOCAL field name —\n * the shape `evaluateDerived` reads for `<field>.<col>` derefs. */\nexport function resolveRowRefs(schema: DerivableSchema, record: DerivableRecord, refRecords: DeriveRefRecords): NonNullable<FormulaContext[\"refs\"]> {\n const refs: NonNullable<FormulaContext[\"refs\"]> = {};\n for (const [key, field] of Object.entries(schema.fields)) {\n if (field.type !== \"ref\" || !field.to) continue;\n const slug = record[key];\n const targets = ownProp(refRecords, field.to);\n refs[key] = typeof slug === \"string\" && targets ? (ownProp(targets, slug) ?? null) : null;\n }\n return refs;\n}\n\n/** True for the field types the saturation loop below (re)computes:\n * `derived` formulas and `flag` predicates. */\nfunction isLoopComputed(field: DerivableFieldSpec): boolean {\n return field.type === \"derived\" || field.type === \"flag\";\n}\n\n/** The value one loop-computed field takes against the current\n * `enriched` record: a `derived` formula result (`null` = failed) or a\n * `flag` predicate match (total — always a boolean). `undefined` for\n * every other field type (nothing to compute). */\nfunction computeFieldValue(\n field: DerivableFieldSpec,\n enriched: DerivableRecord,\n refs: NonNullable<FormulaContext[\"refs\"]>,\n): number | boolean | null | undefined {\n if (field.type === \"derived\" && field.formula) return evaluateDerived(field.formula, { record: enriched, refs });\n if (field.type === \"flag\" && field.where) return matchesWhere(field.where, enriched);\n return undefined;\n}\n\n/** Evaluate every `derived` and `flag` field against `base`, saturating\n * so a computed field can read another one computed in an earlier pass\n * (`subtotal → tax → total` converges in ≤ field-count passes; a flag\n * may read a derived value, or another flag via its stringified\n * boolean). Cycles can't loop forever — passes are bounded by the\n * number of computed fields and the loop breaks as soon as a pass\n * changes nothing. Failed formulas stay ABSENT (the UI renders them as\n * em-dash); flags are total (always true/false). Returns a copy;\n * `base` is never mutated.\n *\n * Computed keys already present in `base` are stripped before\n * evaluation: computed output is host-truth, never persisted-input\n * fallback. A record JSON can carry a stale (or forged) computed value\n * — raw Write/Edit, legacy data — and without the strip, a failing\n * formula would silently surface that value as if the host computed\n * it. */\nexport function deriveAll(schema: DerivableSchema, base: DerivableRecord, refRecords: DeriveRefRecords): DerivableRecord {\n const computedKeys = new Set(\n Object.entries(schema.fields)\n .filter(([, field]) => isLoopComputed(field))\n .map(([key]) => key),\n );\n const enriched: DerivableRecord = Object.fromEntries(Object.entries(base).filter(([key]) => !computedKeys.has(key)));\n const refs = resolveRowRefs(schema, base, refRecords);\n for (let pass = 0; pass < computedKeys.size; pass++) {\n let mutated = false;\n for (const [key, field] of Object.entries(schema.fields)) {\n const next = computeFieldValue(field, enriched, refs);\n if (next !== undefined && next !== null && enriched[key] !== next) {\n enriched[key] = next;\n mutated = true;\n }\n }\n if (!mutated) break;\n }\n return enriched;\n}\n","// Neutralize structural prompt-injection vectors in a short, record-derived\n// string before it rides into an LLM-facing prompt: strip angle brackets,\n// defang backticks / `${` template openings, collapse whitespace (so an\n// embedded newline can't fabricate a pseudo-instruction on its own line), and\n// clip to a small budget. Mirrors the host server's own defang so the two\n// can't drift (#1677).\n\n/** Max chars kept — the first batch of a validation issue is enough to act on. */\nconst DEFANG_MAX_LEN = 200;\n\nexport function defangForPrompt(value: string): string {\n return value.replace(/[<>]/g, \"\").replace(/`/g, \"'\").replace(/\\$\\{/g, \"$ {\").replace(/\\s+/g, \" \").slice(0, DEFANG_MAX_LEN);\n}\n"],"mappings":"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"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { a as isErrorWithCode, c as isStringArray, l as isUnknownArray, s as isRecord, t as errorMessage } from "./dist-D8zokgGo.js";
|
|
2
2
|
import { n as writeFileAtomic } from "./root-BMroU_mB.js";
|
|
3
3
|
import { n as toPosixRelPath } from "./relPath-DW8MC8VO.js";
|
|
4
|
-
import { B as
|
|
5
|
-
import { C as actionVisible, O as firstUnknownDefault, _ as uniqueRefTargets, b as projectBacklinkRow, c as selectDynamicRecord, f as itemIsDone, g as uniqueEmbedTargets, h as uniqueBacklinkSources, i as ownProp, k as schemaDefaults, n as deriveAll, o as firstDateField, s as resolveIcon, t as defangForPrompt, v as backlinkRows, x as rollupValue, y as coerceNumeric } from "./promptSafety-
|
|
6
|
-
import { B as SCHEMA_FILE$1, C as CollectionQueryZ, J as isBackendUnavailable, K as safeSlugName, O as isRegularFile, S as compileJsonlQuery, U as resolveDataDir, V as isContainedInRoot, W as resolveTemplatePath, X as archiveDir, b as queryCsv, c as resolveMutateSet, d as storeFor, f as checkpointSqliteDatabase, ht as stagingSkillDir, i as resolvePrimaryField, it as isPresetSlug$1, j as resolveCreateItemId, m as cacheDir, n as discoverCollections, nt as getWorkspaceRoot, ot as log, r as loadCollection, s as CollectionSchemaZ, u as readOnlyRefusal, y as normalizeCsvValue } from "./discovery-
|
|
4
|
+
import { B as COMPUTED_TYPES, J as fieldTextOrNull, K as storageKindFor, U as embedTargetId, W as isFieldDrivenEvery, d as parseIsoDate, f as parseIsoDateTime, q as fieldText, x as isCanonicalServerTime } from "./itemId-BY2YPBkT.js";
|
|
5
|
+
import { C as actionVisible, O as firstUnknownDefault, _ as uniqueRefTargets, b as projectBacklinkRow, c as selectDynamicRecord, f as itemIsDone, g as uniqueEmbedTargets, h as uniqueBacklinkSources, i as ownProp, k as schemaDefaults, n as deriveAll, o as firstDateField, s as resolveIcon, t as defangForPrompt, v as backlinkRows, x as rollupValue, y as coerceNumeric } from "./promptSafety-Bp780ezk.js";
|
|
6
|
+
import { B as SCHEMA_FILE$1, C as CollectionQueryZ, J as isBackendUnavailable, K as safeSlugName, O as isRegularFile, S as compileJsonlQuery, U as resolveDataDir, V as isContainedInRoot, W as resolveTemplatePath, X as archiveDir, b as queryCsv, c as resolveMutateSet, d as storeFor, f as checkpointSqliteDatabase, ht as stagingSkillDir, i as resolvePrimaryField, it as isPresetSlug$1, j as resolveCreateItemId, m as cacheDir, n as discoverCollections, nt as getWorkspaceRoot, ot as log, r as loadCollection, s as CollectionSchemaZ, u as readOnlyRefusal, y as normalizeCsvValue } from "./discovery-P64fgtuD.js";
|
|
7
7
|
import { ingestStatePath } from "./feeds/paths.js";
|
|
8
8
|
import { mirrorSkillWrite } from "./skill-bridge/index.js";
|
|
9
9
|
import { constants } from "node:fs";
|
|
@@ -393,13 +393,16 @@ function enforcedProblem(key, spec, value) {
|
|
|
393
393
|
* outside the canonical `YYYY-MM-DDTHH:MM[:SS]` shape (e.g. a `Z` suffix,
|
|
394
394
|
* which the day view can't place). `string`-backed types accept anything
|
|
395
395
|
* stringifiable; `ref` existence is out of scope. */
|
|
396
|
+
/** Named in the lint's own message, so the shape is shown rather than
|
|
397
|
+
* described. */
|
|
398
|
+
var CANONICAL_SERVER_TIME_EXAMPLE = "2026-08-15T01:45:54.605987654Z";
|
|
396
399
|
function strictTypeProblem(key, spec, value) {
|
|
397
400
|
switch (spec.type) {
|
|
398
401
|
case "number":
|
|
399
402
|
case "money": return Number.isFinite(coerceNumeric(value)) ? null : `'${key}' = '${String(value)}' is not numeric (a '${spec.type}' field stores a plain number)`;
|
|
400
403
|
case "boolean": return value === true || value === false ? null : `'${key}' = '${String(value)}' is not a boolean (store true or false, unquoted)`;
|
|
401
404
|
case "date": return parseIsoDate(value) !== null ? null : `'${key}' = '${String(value)}' is not a real YYYY-MM-DD date`;
|
|
402
|
-
case "datetime": return parseIsoDateTime(value) !== null ? null : `'${key}' = '${String(value)}' is not a YYYY-MM-DDTHH:MM datetime (seconds optional, no timezone suffix — the shape the calendar parses)`;
|
|
405
|
+
case "datetime": return parseIsoDateTime(value) !== null || isCanonicalServerTime(value) ? null : `'${key}' = '${String(value)}' is not a YYYY-MM-DDTHH:MM datetime (seconds optional, no timezone suffix — the shape the calendar parses), nor a server-stamped instant (${CANONICAL_SERVER_TIME_EXAMPLE})`;
|
|
403
406
|
default: return null;
|
|
404
407
|
}
|
|
405
408
|
}
|
|
@@ -1563,6 +1566,22 @@ var MAX_SCHEMA_ISSUES = 20;
|
|
|
1563
1566
|
* write that reported success would leave a half-filled collection nobody
|
|
1564
1567
|
* knows is half-filled. */
|
|
1565
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.";
|
|
1566
1585
|
/** Refuse an `itemsFile` larger than this, from `stat` and before any read.
|
|
1567
1586
|
* The row cap alone cannot bound the work: the file has to be read and parsed
|
|
1568
1587
|
* WHOLE before there are rows to count, so a huge blob is paid for in full
|
|
@@ -1731,6 +1750,26 @@ async function mergeWithExisting(collection, store, record, itemId) {
|
|
|
1731
1750
|
...record
|
|
1732
1751
|
};
|
|
1733
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
|
+
}
|
|
1734
1773
|
async function putOneItem(collection, store, write, record, mode, deps) {
|
|
1735
1774
|
const { schema } = collection;
|
|
1736
1775
|
const created = mode === "create" ? {
|
|
@@ -1756,7 +1795,10 @@ async function putOneItem(collection, store, write, record, mode, deps) {
|
|
|
1756
1795
|
if (invalid) return reject(itemId, invalid);
|
|
1757
1796
|
}
|
|
1758
1797
|
const result = await write(itemId, toWrite, { refuseOverwrite: mode === "create" });
|
|
1759
|
-
if (result.kind === "ok") return {
|
|
1798
|
+
if (result.kind === "ok") return {
|
|
1799
|
+
written: result.itemId,
|
|
1800
|
+
...lintOf(toWrite, result.itemId, schema, deps)
|
|
1801
|
+
};
|
|
1760
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)`);
|
|
1761
1803
|
if (result.kind === "conflict") return reject(itemId, `'${itemId}' already exists — mode "create" refuses overwrite; use "upsert" to update it`);
|
|
1762
1804
|
return reject(itemId, "write refused: the collection's data dir escapes the workspace");
|
|
@@ -1929,23 +1971,51 @@ async function resolvePutRows(args, deps) {
|
|
|
1929
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.`;
|
|
1930
1972
|
return rows;
|
|
1931
1973
|
}
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
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) {
|
|
1938
1992
|
const written = [];
|
|
1939
1993
|
const rejected = [];
|
|
1994
|
+
const lint = [];
|
|
1940
1995
|
for (const record of rows) {
|
|
1941
|
-
const outcome = await putOneItem(collection, store, write, record,
|
|
1996
|
+
const outcome = await putOneItem(collection, store, write, record, mode, deps);
|
|
1942
1997
|
if (outcome.written) written.push(outcome.written);
|
|
1943
1998
|
if (outcome.rejected) rejected.push(outcome.rejected);
|
|
1999
|
+
if (outcome.lint) lint.push(outcome.lint);
|
|
1944
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);
|
|
1945
2014
|
return JSON.stringify({
|
|
1946
2015
|
collection: collection.slug,
|
|
1947
2016
|
written,
|
|
1948
|
-
rejected
|
|
2017
|
+
rejected,
|
|
2018
|
+
...lintReport(lint)
|
|
1949
2019
|
});
|
|
1950
2020
|
}
|
|
1951
2021
|
/** Delete records by id, through the store — so it works on any writable
|
|
@@ -2155,7 +2225,7 @@ async function handlePutSchema(slug, schemaArg, deps) {
|
|
|
2155
2225
|
written: true
|
|
2156
2226
|
});
|
|
2157
2227
|
}
|
|
2158
|
-
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`
|
|
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.";
|
|
2159
2229
|
/** Validate getItems' optional `ids`/`fields` args, then delegate. */
|
|
2160
2230
|
async function dispatchGetItems(collection, args, deps) {
|
|
2161
2231
|
const ids = optionalStringArray(args.ids, "ids");
|
|
@@ -2211,7 +2281,7 @@ async function dispatchManageCollection(deps, args) {
|
|
|
2211
2281
|
}
|
|
2212
2282
|
var MANAGE_COLLECTION_DEFINITION = {
|
|
2213
2283
|
name: "manageCollection",
|
|
2214
|
-
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
|
|
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.",
|
|
2215
2285
|
inputSchema: {
|
|
2216
2286
|
type: "object",
|
|
2217
2287
|
properties: {
|
|
@@ -2286,6 +2356,6 @@ function makeManageCollectionTool(deps = {}) {
|
|
|
2286
2356
|
};
|
|
2287
2357
|
}
|
|
2288
2358
|
//#endregion
|
|
2289
|
-
export {
|
|
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 };
|
|
2290
2360
|
|
|
2291
|
-
//# sourceMappingURL=server-
|
|
2361
|
+
//# sourceMappingURL=server-BvXO49Vj.js.map
|