@mulmoclaude/core 4.0.1 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/assets/helps/collection-skills.md +6 -1
  2. package/dist/collection/core/draft.d.ts +9 -1
  3. package/dist/collection/core/fieldDefaults.d.ts +28 -0
  4. package/dist/collection/core/schemaZ.d.ts +3 -0
  5. package/dist/collection/index.cjs +18 -7
  6. package/dist/collection/index.cjs.map +1 -1
  7. package/dist/collection/index.d.ts +1 -0
  8. package/dist/collection/index.js +16 -8
  9. package/dist/collection/index.js.map +1 -1
  10. package/dist/collection/registry/server/index.cjs +2 -2
  11. package/dist/collection/registry/server/index.js +2 -2
  12. package/dist/collection/server/index.cjs +2 -2
  13. package/dist/collection/server/index.js +2 -2
  14. package/dist/collection-watchers/index.cjs +3 -3
  15. package/dist/collection-watchers/index.js +3 -3
  16. package/dist/{discovery-C_3gXoin.cjs → discovery-B9EESjHP.cjs} +15 -3
  17. package/dist/{discovery-C_3gXoin.cjs.map → discovery-B9EESjHP.cjs.map} +1 -1
  18. package/dist/{discovery-CQp0wi-n.js → discovery-D88x2DmW.js} +15 -3
  19. package/dist/{discovery-CQp0wi-n.js.map → discovery-D88x2DmW.js.map} +1 -1
  20. package/dist/feeds/server/index.cjs +2 -2
  21. package/dist/feeds/server/index.js +2 -2
  22. package/dist/google/index.cjs +1 -1
  23. package/dist/google/index.js +1 -1
  24. package/dist/{promptSafety-CSdyp7RF.js → promptSafety-CJ-sC1pX.js} +43 -2
  25. package/dist/promptSafety-CJ-sC1pX.js.map +1 -0
  26. package/dist/{promptSafety-CWqYq-NS.cjs → promptSafety-CuczAN9d.cjs} +60 -1
  27. package/dist/promptSafety-CuczAN9d.cjs.map +1 -0
  28. package/dist/{server-CF_tGCGP.cjs → server-DSrRdPvf.cjs} +11 -5
  29. package/dist/{server-CF_tGCGP.cjs.map → server-DSrRdPvf.cjs.map} +1 -1
  30. package/dist/{server-BIewQYpC.js → server-dijB4BYv.js} +11 -5
  31. package/dist/{server-BIewQYpC.js.map → server-dijB4BYv.js.map} +1 -1
  32. package/package.json +1 -1
  33. package/dist/promptSafety-CSdyp7RF.js.map +0 -1
  34. package/dist/promptSafety-CWqYq-NS.cjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"discovery-CQp0wi-n.js","names":[],"sources":["../src/host/hostSlot.ts","../src/collection/server/host.ts","../src/collection/server/backendAvailability.ts","../src/collection/server/paths.ts","../src/collection/server/storePage.ts","../src/collection/server/firestoreStore.ts","../src/collection/server/appManifest.ts","../src/collection/server/io.ts","../src/collection/core/queryZ.ts","../src/collection/server/csvQuery.ts","../src/collection/server/csvStore.ts","../src/collection/server/watchFs.ts","../src/collection/server/sqliteStore.ts","../src/collection/server/store.ts","../src/collection/core/mutateAction.ts","../src/collection/core/schemaRules.ts","../src/collection/core/schemaZ.ts","../src/collection/server/discovery.ts"],"sourcesContent":["// Shared host-injection primitives for the server-side engines. Each engine is\n// parameterized over host-owned dependencies (workspace root, logger, file\n// writer, …) that would be an uphill import if pulled in directly. Instead the\n// host wires them ONCE at startup and the engine reads them back through a slot.\n// This file centralises the storage/throw/no-op contract so the three domain\n// bindings (collection, feeds, google) can't drift into subtly different\n// behaviour.\n\nimport type { StructuredLogger } from \"@mulmoclaude/common\";\n\n/** Structured logger shape the engines log through — `(prefix, message,\n * data?)`, matching the host `Logger`. The canonical declaration lives in\n * `@mulmoclaude/common`; the domains alias it under their public names\n * (`CollectionLogger` / `FeedsLogger` / `GoogleLogger`). */\nexport type { StructuredLogger };\n\n/** A single host-injected dependency, wired once at boot and read back by the\n * engine. `get()` fails loudly when the host never configured (the engine\n * cannot operate without it); `peek()` stays quiet for non-critical reads. */\nexport interface HostSlot<T> {\n /** Wire the value. Re-binding to a *different* value throws — silently\n * redirecting a configured engine to another host would be a bug. Re-binding\n * the same value is a no-op. */\n set: (value: T) => void;\n /** The configured value, or throw (with the slot name) if never set. */\n get: () => T;\n /** The configured value, or `null` if never set — never throws. */\n peek: () => T | null;\n /** Test-only: forget the configured value. */\n reset: () => void;\n}\n\nexport function createHostSlot<T>(name: string): HostSlot<T> {\n let current: T | null = null;\n const set = (value: T): void => {\n if (current !== null && current !== value) {\n throw new Error(`${name} was already called with a different host`);\n }\n current = value;\n };\n const get = (): T => {\n if (current === null) throw new Error(`${name} was not called by the host`);\n return current;\n };\n return { set, get, peek: () => current, reset: () => (current = null) };\n}\n\n/** Build the 4-method forwarding logger over a `() => StructuredLogger | null`\n * getter. Calls made before a logger is available are dropped — logging is\n * non-critical, unlike a required getter which fails loudly. Centralised so no\n * domain can drift into throwing where a sibling drops. */\nexport function createForwardingLogger(getLogger: () => StructuredLogger | null): StructuredLogger {\n return {\n error: (prefix, message, data) => getLogger()?.error(prefix, message, data),\n warn: (prefix, message, data) => getLogger()?.warn(prefix, message, data),\n info: (prefix, message, data) => getLogger()?.info(prefix, message, data),\n debug: (prefix, message, data) => getLogger()?.debug(prefix, message, data),\n };\n}\n","// Host binding for the server-side collection engine.\n//\n// The engine is parameterized over the host's workspace + services, but\n// threading those through every call would be invasive. Instead each host\n// (MulmoClaude, MulmoTerminal) configures the binding ONCE at startup via\n// `configureCollectionHost`, and the engine reads it through the getters\n// below. This keeps the existing call sites (which default to the live\n// workspace root) unchanged while removing the package's dependency on\n// host-only modules (`server/workspace/workspace.ts`, the host logger).\n\nimport path from \"node:path\";\nimport { canonicalRoot } from \"../../files/root.js\";\nimport { localCollectionKeyOf, sharedCollectionKey, type CollectionKey } from \"../core/collectionKey.js\";\nimport { createForwardingLogger, createHostSlot, type StructuredLogger } from \"../../host/hostSlot.js\";\n// Type-only: this module must not pull the firebase SDK in at runtime (it is an\n// OPTIONAL peer of this package). The adapter ships from\n// `@mulmoclaude/core/collection/firestore` instead.\nimport type { FirestoreDocs } from \"./firestoreDocs\";\n\n/** Public alias of the shared `StructuredLogger` — keeps the domain surface name-stable. */\nexport type CollectionLogger = StructuredLogger;\n\n/** Re-exported so collection-engine callers (the watcher, the reconciler) keep\n * one import surface; the definition lives in `@mulmoclaude/core/files`\n * because a root is an identity in subsystems that do not depend on this one. */\nexport { canonicalRoot };\n\n/** `err.code` on the throw from `getWorkspaceRoot()` under an explicit-root\n * binding: this CALL is missing its `workspaceRoot` option. Exported because\n * the fix differs from a watcher root conflict — that one means another root's\n * watcher is already running — and a host catching both in one place should\n * not have to match on message text to tell them apart. */\nexport const COLLECTION_ROOT_REQUIRED = \"COLLECTION_ROOT_REQUIRED\";\n\n/** Build a local {@link CollectionKey}, canonicalising the root. The identity\n * type itself is isomorphic and cannot canonicalise (that needs `node:path`),\n * so this is the constructor server code should use. */\nexport const localCollectionKey = (root: string, slug: string): CollectionKey => localCollectionKeyOf(canonicalRoot(root), slug);\n\n/** INVARIANT — **a slug is unique within a root and nowhere else.**\n *\n * A collection's identity is `(root, slug)`. Anything keyed by slug ALONE —\n * a cache, a pubsub channel, a view token, a notification id, a rendered\n * card — is a cross-root collision waiting to happen the moment two projects\n * each own a `tasks` collection. A single-workspace host never sees it, which\n * is exactly why it keeps being written that way.\n *\n * Every engine surface that crosses a host boundary therefore carries the\n * root: `CollectionChangePayload.root`, the completion bell's legacy id, the\n * presented card's scope. When you add another, key it on the pair. */\nexport interface CollectionHost {\n /** Absolute path to the host workspace root (e.g. `~/mulmoclaude`). The\n * default root for every path/containment check that isn't given an\n * explicit override.\n *\n * `null` puts the binding in EXPLICIT-ROOT mode: the host declares that it\n * always passes `opts.workspaceRoot` per call, and `getWorkspaceRoot()`\n * throws instead of guessing. A multi-root host (MulmoTerminal, one root\n * per project) wants this — there, a forgotten option is not a crash but a\n * silent read/write against the WRONG project. A single-workspace host\n * (MulmoClaude) passes a string and nothing changes. */\n workspaceRoot: string | null;\n /** Host logger; the engine logs under the `\"collections\"` prefix. */\n log: CollectionLogger;\n /** Host workspace layout — supplied as the host's own path helpers so the\n * package owns no layout literals and works against a test/alt root. */\n paths: {\n /** Absolute user-scope skills dir for a root (host-specific, e.g.\n * `~/.claude/skills`), or `null` when this root has NO user scope.\n *\n * A single-workspace host (MulmoClaude) returns the same path for its one\n * root and behaves exactly as before — user scope merges into the\n * workspace and a project slug still shadows a user one.\n *\n * A multi-root host returns `null` for a plain project directory: `~` and\n * a project are separate worlds, and a project that could resolve a\n * machine-global collection would depend on something no clone of it can\n * have. Under `null` the user pass is skipped in BOTH discovery and\n * `loadCollection` — a user-only slug is then a miss, not a quiet hop\n * into another world. Resolution is where the guarantee has to hold:\n * filtering only the listing would still let a slug typed by the agent,\n * or arriving in a URL, write into `~/.claude/skills` from a project.\n *\n * A bare `string` is the pre-3.3.0 form and still works, read as \"this\n * path, for every root\". It is accepted rather than required-away because\n * a caret range floats across minors: a host pinned at `^3.2.0` installs\n * this version without touching its code, and a required callable would\n * turn that into a TypeError on its first discovery — a crash, not an\n * opt-out. Prefer the function form; the string is deprecated. */\n userSkillsDir: string | ((workspaceRoot: string) => string | null);\n /** Absolute project-scope skills dir for a workspace (`<root>/.claude/skills`). */\n projectSkillsDir: (workspaceRoot: string) => string;\n /** Absolute feeds-registry root for a workspace (`<root>/data/feeds`). */\n feedsRoot: (workspaceRoot: string) => string;\n /** Absolute project-skills *staging* dir for a root (`<root>/data/skills`),\n * or `null` when this root has NO staging tree.\n *\n * Staging exists because a managed workspace gates writes into `.claude/`\n * and a skill-bridge hook mirrors `data/skills/<slug>/` across. A root\n * with no such bridge (a plain project folder) has nothing to mirror, so\n * the skill dir IS the authoring location and there is no staging.\n *\n * Return `null` there — do NOT hand back the skill dir instead. It looks\n * equivalent (the read list becomes the same dir twice) but the delete\n * path `rm -rf`s the staging dir by name, which would then remove the\n * committed skill under the label \"staging\". */\n skillsStagingDir: (workspaceRoot: string) => string | null;\n /** Workspace-relative archive dir (a removed collection's files move here). */\n archiveDir: string;\n /** Absolute path to the user-supplied extra-registries config file for a\n * workspace (`<root>/config/collections-registries.json`). Injected so the\n * registry engine owns no app layout literal and a downstream host can point\n * it at its own workspace. */\n collectionsRegistriesConfig: (workspaceRoot: string) => string;\n };\n /** True for a preset-skill slug (host-owned naming convention). */\n isPresetSlug: (slug: string) => boolean;\n}\n\n/** The authenticated Firestore access a shared collection is served through.\n *\n * It carries NO uid, and that is a decision rather than an omission. Nothing\n * about a shared collection is keyed by uid any more: the documents live at\n * `apps/{aid}/collections/{cid}/items`, and the deployed rules authorize on\n * `request.auth.token.email` against the app's member roster. A uid kept\n * \"just for identity\" would be a value nothing checks, sitting next to a path\n * it no longer determines — which is how a later reader ends up deriving a\n * path from it again.\n *\n * `email` is what the rules actually evaluate, so it is what makes a refusal\n * explainable: `permission-denied` is the most common failure a shared\n * collection has and the least informative, and naming the principal turns it\n * into \"signed in as a@b — this app's roster may not list you\". A session with\n * no verified email is NOT a session here (the accessor answers null): public\n * anonymous submission is a visitor's path through the published web app, not\n * the host's.\n *\n * `docs` is the narrow document interface rather than a raw `Firestore` so the\n * backend stays testable: core ships `createFirestoreDocs` over the real SDK\n * (`firestoreDocs.ts`) and tests inject an in-memory fake. */\nexport interface FirestoreHandle {\n docs: FirestoreDocs;\n email: string;\n /** The signed-in Firebase uid.\n *\n * The EMAIL is the principal the roster is keyed by, and it is what every\n * record operation needs. The uid is needed by exactly one caller —\n * `publish` — because the app document's `owner` is a uid: the rules\n * require `owner == request.auth.uid` when the app is created and require\n * it unchanged afterwards. Required rather than optional so a host cannot\n * wire a session that can read and write records but silently cannot\n * create an app; the failure would surface as a permission denial with\n * nothing in it about identity. */\n uid: string;\n}\n\n/** A collection's records changed on disk. Carries the `slug` so the host can\n * publish on a per-collection channel; `ids` lists the affected record ids\n * when known (a consumer may ignore them and refetch the whole collection),\n * and `op` is advisory. Deliberately carries NO record bodies — this is a\n * \"refetch\" ping, not a data feed, so it stays cheap and leaks nothing when a\n * host relays it into an opaque-origin custom-view iframe. */\n/** Fields every change carries, whatever kind of collection it is about. */\ninterface CollectionChangeBase {\n /** The collection's NAME in its scope: the slug, or a shared collection's\n * `cid`. Never an identity on its own — see the two arms below. */\n slug: string;\n ids?: string[];\n op?: \"upsert\" | \"delete\";\n}\n\n/** A change to a collection in a directory. */\nexport interface LocalCollectionChange extends CollectionChangeBase {\n /** Absolute workspace/project root the change happened under. Present only\n * when the engine call carried an explicit `workspaceRoot`; absent means the\n * host's configured root, so a single-workspace host never sets it.\n * A multi-root host MUST key its live-update fan-out on `(root, slug)` —\n * two projects each owning a `tasks` collection would otherwise refresh each\n * other's open views. */\n root?: string;\n /** Never on a local change. The two arms are mutually exclusive at the TYPE\n * level, not by convention: a payload carrying both would be read as shared\n * by `collectionChangeKey`, which drops the root and fans the update out on\n * the wrong channel. */\n aid?: never;\n}\n\n/** A change to a collection published to a shared app. */\nexport interface SharedCollectionChange extends CollectionChangeBase {\n /** The shared app (`apps/{aid}/collections/{cid}`). Required on this arm:\n * it is what makes the payload an identity. */\n aid: string;\n /** Never on a shared change — a shared collection has no root, because the\n * same collection is resolved from every clone of the repository. */\n root?: never;\n}\n\n/** A collection's records changed. Carries the `slug` so the host can publish\n * on a per-collection channel; `ids` lists the affected record ids when known\n * (a consumer may ignore them and refetch the whole collection), and `op` is\n * advisory. Deliberately carries NO record bodies — this is a \"refetch\" ping,\n * not a data feed, so it stays cheap and leaks nothing when a host relays it\n * into an opaque-origin custom-view iframe.\n *\n * Reading a payload is unchanged: `slug`, `root`, `ids` and `op` are all\n * reachable on the union. Use {@link collectionChangeKey} rather than deciding\n * what an absent field means by hand. */\nexport type CollectionChangePayload = LocalCollectionChange | SharedCollectionChange;\n\ntype CollectionChangePublisher = (payload: CollectionChangePayload) => void;\n\n/** Build a change payload, attaching `root` only when the engine call carried\n * an explicit one. Centralised so every publish site states the root the same\n * way, and so a single-workspace host's payload shape stays byte-identical to\n * what it saw before multi-root support. */\nexport function collectionChangePayload(base: CollectionChangeBase, root: string | undefined): LocalCollectionChange {\n // Canonical, because a host keys its live-update fan-out on this value: a\n // direct `writeItem({ workspaceRoot: \"/proj/\" })` and the watcher's own\n // publish for `/proj` must land on the same channel, not two.\n return root === undefined ? base : { ...base, root: canonicalRoot(root) };\n}\n\n/** Build a change payload for a SHARED collection. `slug` carries the `cid`,\n * which is what it is called inside its app; `aid` is what makes it an\n * identity. Never stamps a `root` — a shared collection does not have one. */\nexport function sharedCollectionChangePayload(base: CollectionChangeBase, aid: string): SharedCollectionChange {\n // Validated HERE, at construction, not at publish. A host's publisher wraps\n // its publish in a try/catch on purpose -- dropping one live-refresh event\n // beats crashing the write that triggered it -- so a name that no channel can\n // encode would be swallowed there and the update would simply stop arriving,\n // with nothing said. Building the key is the cheap way to say it loudly.\n sharedCollectionKey(aid, base.slug);\n return { ...base, aid };\n}\n\n/** The identity a change is about, as a value — the thing to key a fan-out on.\n *\n * This is the one place that decides what an absent field means, so no host\n * has to: `aid` present is a shared collection, otherwise it is local, and a\n * local payload with no `root` means the host's configured root (which is why\n * a single-workspace host's payloads still say nothing about roots).\n *\n * `fallbackRoot` is what a payload with no root resolves to. An explicit-root\n * host has no such default and must not guess — pass the root the call was\n * made for. */\n/** Refuse a payload that names both an app and a root.\n *\n * The two arms are mutually exclusive in the TYPE, so this cannot come from\n * the constructors -- it is corrupt input (a JS caller, a cast, something off\n * a wire). Throwing rather than picking one: whichever way it were guessed,\n * the update would be fanned out on a channel it does not belong to, and that\n * is invisible where a throw is not.\n *\n * Takes the WIDENED shape deliberately. Written inline, TypeScript narrows the\n * union to `never` inside the branch and the fields cannot be read at all --\n * which is the type-level guarantee doing its job, and exactly why the runtime\n * check has to be stated somewhere it can still see them. */\nfunction requireOneScope(payload: CollectionChangeBase & { aid?: string; root?: string }): void {\n if (payload.aid !== undefined && payload.root !== undefined) {\n throw new Error(`collectionChangeKey: payload for \"${payload.slug}\" carries both an app (${payload.aid}) and a root (${payload.root})`);\n }\n}\n\nexport function collectionChangeKey(payload: CollectionChangePayload, fallbackRoot: string): CollectionKey {\n requireOneScope(payload);\n return payload.aid === undefined ? localCollectionKey(payload.root ?? fallbackRoot, payload.slug) : sharedCollectionKey(payload.aid, payload.slug);\n}\n\nconst hostSlot = createHostSlot<CollectionHost>(\"@mulmoclaude/core/collection/server: configureCollectionHost()\");\nlet changePublisher: CollectionChangePublisher | null = null;\nlet firestoreAccessor: (() => FirestoreHandle | null) | null = null;\nlet sharedCollectionsSupported = false;\n\n/** Wire the engine to a host. Call once at server startup, before any\n * collection storage operation. Re-binding to a *different* host throws —\n * silently redirecting later filesystem operations to another workspace\n * would be a bug, not a feature. Re-calling with the same host is a no-op. */\nexport function configureCollectionHost(host: CollectionHost): void {\n hostSlot.set(host);\n}\n\n/** Wire a publisher that broadcasts record-change events; the host bridges it\n * to its pubsub. Kept SEPARATE from `configureCollectionHost` because the\n * host's pubsub instance isn't ready at host-binding time (the binding is set\n * at the top of server startup, the pubsub later). Optional: left unset, every\n * write is silent — the default for tests and for a host that doesn't want\n * live view updates. Pass `null` to detach (test teardown). */\nexport function setCollectionChangePublisher(publish: CollectionChangePublisher | null): void {\n changePublisher = publish;\n}\n\n/** Broadcast a record-change event if a publisher is wired (no-op otherwise).\n * Called from the write path (`writeItem`/`deleteItem`). The wired publisher is\n * expected to be fire-and-forget (it wraps its own pubsub call in try/catch),\n * so this stays a thin pass-through and never throws into the write. */\nexport function publishCollectionChange(payload: CollectionChangePayload): void {\n changePublisher?.(payload);\n}\n\n/** Does this host serve SHARED (firestore-backed) collections at all?\n *\n * Opt-in, and `false` until a host says otherwise, because the default has to\n * be the safe one: a host that cannot reach Firestore must not accept a\n * shared schema and then report the collection as empty.\n *\n * WHY A CAPABILITY AND NOT A HOST CHECK. The engine must not know which host\n * it is running in. The rule being expressed — shared collections live in a\n * PROJECT REPOSITORY, not in a single managed workspace where one roster\n * would govern every unrelated collection beside it — is a property the host\n * knows about ITSELF. Asking the engine to test for a particular host's\n * workspace would put a host's name in shared code and make every change to\n * a one-host feature a change to this package.\n *\n * WHY NOT A FIELD ON `CollectionHost`. Same reason the accessor below is not\n * one: `configureCollectionHost` is a ONE-SHOT binding a host sets at startup\n * and cannot re-bind, so a suite exercising this engine could not turn the\n * capability on for itself without owning the whole host. This is the same\n * concern as the accessor and belongs beside it.\n *\n * NOT DERIVED FROM THE ACCESSOR. \"Has a session right now\" and \"serves shared\n * collections at all\" are different questions: the accessor answers null\n * between connections, and a collection must not stop being ACCEPTABLE\n * because nobody is signed in — that would turn \"connect first\" into \"this\n * schema is invalid\". */\nexport function setSharedCollectionsSupport(supported: boolean): void {\n sharedCollectionsSupported = supported;\n}\n\n/** Whether the host declared support. Consulted from the schema ACCEPTANCE\n * gate, whose contract is to return a reason rather than raise. */\nexport function hostSupportsSharedCollections(): boolean {\n return sharedCollectionsSupported;\n}\n\n/** Wire the accessor for the host's authenticated Firestore session.\n *\n * Separate from `configureCollectionHost` for the same reason\n * `setCollectionChangePublisher` is: the host binding is set at the top of\n * server startup, but this session doesn't exist until the user connects\n * remote-host (and closes again on disconnect), so it cannot be part of a\n * one-shot binding. Optional — left unwired, only shared collections are\n * affected, and they report \"not connected\". Pass `null` to detach. */\nexport function setFirestoreAccessor(accessor: (() => FirestoreHandle | null) | null): void {\n firestoreAccessor = accessor;\n}\n\n/** The host's live Firestore access, or null when there is no session (or the\n * host never wired one — every non-shared backend leaves it unset).\n * Callers MUST surface null as an actionable \"connect remote-host first\",\n * never as an empty result: silence would be indistinguishable from a\n * collection that genuinely has no records. */\nexport function firestoreHandle(): FirestoreHandle | null {\n return firestoreAccessor?.() ?? null;\n}\n\nfunction requireHost(): CollectionHost {\n return hostSlot.get();\n}\n\n/** The configured workspace root. Throws if the host never configured one —\n * and, under an explicit-root binding (`workspaceRoot: null`), throws rather\n * than guessing, which is the whole point of that mode: on a multi-root host\n * a missing `opts.workspaceRoot` must fail loudly instead of silently\n * resolving against some other project. */\nexport function getWorkspaceRoot(): string {\n const root = requireHost().workspaceRoot;\n if (root === null) {\n throw Object.assign(\n new Error(\n \"@mulmoclaude/core/collection/server: the host is bound in explicit-root mode (workspaceRoot: null), \" +\n \"so there is no ambient workspace root — pass an explicit `workspaceRoot` in this call's options.\",\n ),\n { code: COLLECTION_ROOT_REQUIRED },\n );\n }\n return root;\n}\n\n/** The configured workspace root, or `null` under an explicit-root binding /\n * before the host configures one. Never throws — for callers that need to\n * COMPARE roots (is this the one we are already running for?) rather than\n * operate on one. Anything that will touch the filesystem wants\n * `getWorkspaceRoot()` and its loud failure instead. */\nexport function peekWorkspaceRoot(): string | null {\n return hostSlot.peek()?.workspaceRoot ?? null;\n}\n\n// Workspace-layout accessors — thin wrappers over the host binding, named to\n// match the host helpers they replace so the moved engine modules keep their\n// call sites. Each throws (via requireHost) if the host never configured.\n/** The user-scope skills dir for a root, or `null` when it has none. The one\n * place the pre-3.3.0 `string` binding is normalized, so nothing downstream\n * has to know the host might not have upgraded its shape yet. */\nexport function userSkillsDir(workspaceRoot: string): string | null {\n const binding = requireHost().paths.userSkillsDir;\n return typeof binding === \"string\" ? binding : binding(workspaceRoot);\n}\nexport function projectSkillsDir(workspaceRoot: string): string {\n return requireHost().paths.projectSkillsDir(workspaceRoot);\n}\nexport function feedsRoot(workspaceRoot: string): string {\n return requireHost().paths.feedsRoot(workspaceRoot);\n}\nexport function skillsStagingDir(workspaceRoot: string): string | null {\n return requireHost().paths.skillsStagingDir(workspaceRoot);\n}\n\n/** `<staging>/<slug>` for a root, or `null` when the root has no staging tree.\n * The single place the staging-or-not branch is spelled, so every caller\n * (read bases, schema write targets, archive, delete) agrees on it. */\nexport function stagingSkillDir(workspaceRoot: string, slug: string): string | null {\n const staging = requireHost().paths.skillsStagingDir(workspaceRoot);\n return staging === null ? null : path.join(staging, slug);\n}\nexport function archiveDir(): string {\n return requireHost().paths.archiveDir;\n}\n/** Absolute path to a workspace's `collections-registries.json`. Takes the\n * root explicitly — reading the ambient one here would throw under an\n * explicit-root binding and take the Discover tab down with it. */\nexport function collectionsRegistriesConfigPath(workspaceRoot: string): string {\n return requireHost().paths.collectionsRegistriesConfig(workspaceRoot);\n}\nexport function isPresetSlug(slug: string): boolean {\n return requireHost().isPresetSlug(slug);\n}\n\n/** Logger proxy so engine modules can `import { log }` and use it exactly like\n * the host logger — each call forwards to the live host binding. Logging is\n * non-critical, so calls before the host configures a binding (e.g. unit tests\n * that exercise pure logic) are dropped rather than throwing — unlike\n * `getWorkspaceRoot()`, which fails loudly because the engine cannot operate\n * without a workspace root. */\nexport const log: CollectionLogger = createForwardingLogger(() => hostSlot.peek()?.log ?? null);\n","// \"The backend can't serve this right now\" — as distinct from \"the record\n// isn't there\" or \"the stored record is malformed\".\n//\n// Both non-file backends have such a state, and neither is a data problem:\n//\n// - sqlite: `node:sqlite` needs Node >= 22.5, and the app's floor is 20.12,\n// so on an older runtime ONLY sqlite collections fail (sqliteStore.ts).\n// - csv/dataSource: `@duckdb/node-api` is a native module whose prebuilt\n// binding can be missing for the platform (csvStore.ts).\n//\n// The distinction matters because the layers above catch broadly. Without a\n// type to test, `store.read(...).catch(() => null)` reports \"record missing\",\n// a merge reports \"malformed stored file\", and an ontology count reports 0 —\n// each of which sends the agent after a data problem that does not exist, and\n// the last of which can have it offer to recreate records that are intact.\n//\n// Anything that summarises or swallows a store error MUST re-check with\n// `isBackendUnavailable` and let it through.\n\n/** Thrown by a store when its engine or session cannot serve the request. */\nexport class BackendUnavailableError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"BackendUnavailableError\";\n }\n}\n\nexport function isBackendUnavailable(err: unknown): err is BackendUnavailableError {\n return err instanceof BackendUnavailableError;\n}\n","// Path helpers + safe-slug guard for the collections module. Mirrors the\n// pattern used by `server/workspace/skills/catalog.ts` so CodeQL's\n// `js/path-injection` sanitiser recognises our taint-launder.\n\nimport path from \"node:path\";\nimport { realpathSync } from \"node:fs\";\nimport { getWorkspaceRoot } from \"./host\";\n// The character rules live in ../core/ids so the isomorphic schema validator\n// (../core/schemaZ) gates on the SAME patterns these sanitisers do.\nimport { SAFE_SLUG_PATTERN, SAFE_RECORD_ID_PATTERN } from \"../core/ids\";\nimport { isErrorWithCode } from \"@mulmoclaude/common\";\n\nexport const SCHEMA_FILE = \"schema.json\";\n\n/** Sanitise a user-supplied slug into a safe directory-name leaf.\n * Returns null for anything that fails the slug whitelist OR isn't a\n * basename (i.e. survives `path.basename` round-trip unchanged).\n * The basename round-trip is the pattern CodeQL recognises as a\n * `js/path-injection` sanitiser. */\nexport function safeSlugName(slug: string): string | null {\n if (typeof slug !== \"string\") return null;\n if (!SAFE_SLUG_PATTERN.test(slug)) return null;\n const basename = path.basename(slug);\n if (basename !== slug) return null;\n return basename;\n}\n\n/** Sanitise a user-supplied record id into a safe filename stem. Like\n * `safeSlugName` but tolerates interior dots (so natural keys work),\n * while still rejecting any `..` substring, path separators, and\n * leading/trailing dots. The `path.basename` round-trip is the same\n * `js/path-injection` sanitiser CodeQL recognises on `safeSlugName`. */\nexport function safeRecordId(recordId: string): string | null {\n if (typeof recordId !== \"string\") return null;\n if (!SAFE_RECORD_ID_PATTERN.test(recordId)) return null;\n if (recordId.includes(\"..\")) return null;\n const basename = path.basename(recordId);\n if (basename !== recordId) return null;\n return basename;\n}\n\n/** Realpath the closest existing ancestor of `absPath` and return it.\n * Returns null if no ancestor exists or if the realpath call fails\n * for a non-ENOENT reason (permissions, etc.). Used by\n * `containedPath` to defend against symlinks pointing outside the\n * workspace even when the leaf hasn't been created yet. */\nfunction realpathClosestAncestor(absPath: string): string | null {\n let cursor = absPath;\n while (cursor !== path.dirname(cursor)) {\n try {\n return realpathSync(cursor);\n } catch (err) {\n if (isErrorWithCode(err) && err.code === \"ENOENT\") {\n cursor = path.dirname(cursor);\n continue;\n }\n return null;\n }\n }\n return null;\n}\n\n/** True iff the realpath'd closest existing ancestor of `absPath`\n * resolves under `rootPath`'s realpath. Pure helper, takes both\n * paths explicitly so tests can drive it against a `mkdtempSync`\n * root without touching the user's workspace. Defends against the\n * data dir or any ancestor being a symlink to a directory outside\n * the workspace — lexical-only checks (`path.resolve` + prefix\n * match) would miss this case, which is the class of bug the rest\n * of this codebase uses realpath-based containment to avoid (see\n * `server/utils/files/safe.ts#resolveWithinRoot`). */\nexport function isContainedInRoot(absPath: string, rootPath: string): boolean {\n let rootReal: string;\n try {\n rootReal = realpathSync(rootPath);\n } catch {\n return false;\n }\n const ancestorReal = realpathClosestAncestor(absPath);\n if (ancestorReal === null) return false;\n if (ancestorReal === rootReal) return true;\n return ancestorReal.startsWith(rootReal + path.sep);\n}\n\n// NOTE: there is deliberately no `isContainedInWorkspace(absPath)` helper here.\n// It existed, had zero callers, and was exactly the shape a contributor reaches\n// for by reflex — at which point containment is checked against the AMBIENT\n// root while the operation it guards runs under an explicit one. Pass the root\n// you are operating on to `isContainedInRoot` instead.\n\n/** Resolve a schema-declared dataPath against `rootPath` (default:\n * the live workspace), refusing anything that escapes — absolute\n * paths, `..`-segments, empty string, or symlinks pointing outside\n * the root. Returns the absolute path on success, null on refusal.\n * Does NOT require the directory to exist; the caller may create it\n * on first write. The realpath containment check covers the symlink\n * case at discovery time; io operations re-check before each write\n * to defend against symlinks introduced between discovery and use.\n *\n * `rootPath` exists as an optional override so a test (or a tool\n * driving discovery against a `mkdtempSync` tree) gets a dataDir\n * rooted at the same place it asked to scan, not the real workspace.\n * Without this, `discoverApps({ workspaceRoot: tmpdir })` would\n * discover skills in tmpdir but resolve every app's dataDir against\n * `~/mulmoclaude/`, breaking isolation. */\nexport function resolveDataDir(dataPath: string, rootPath: string = getWorkspaceRoot()): string | null {\n if (typeof dataPath !== \"string\" || dataPath.length === 0) return null;\n if (path.isAbsolute(dataPath)) return null;\n const normalized = path.normalize(dataPath);\n if (normalized.startsWith(\"..\") || normalized.includes(`${path.sep}..${path.sep}`)) return null;\n const resolved = path.resolve(rootPath, normalized);\n if (!isContainedInRoot(resolved, rootPath)) return null;\n return resolved;\n}\n\n/** Compose the absolute path to a single record file. Both arguments\n * must have been passed through `safeSlugName` / `resolveDataDir`\n * before reaching here so the join can't escape. */\nexport function itemFilePath(dataDir: string, itemId: string): string {\n return path.join(dataDir, `${itemId}.json`);\n}\n\n/** Resolve an action's skill-relative `template` path against\n * `skillDir`, refusing escapes — absolute paths, `..`-segments, or a\n * symlink pointing outside the skill dir. Mirrors `resolveDataDir`;\n * the realpath containment is the hard guarantee. Returns the\n * absolute path on success, null on refusal. */\nexport function resolveTemplatePath(skillDir: string, templateRelPath: string): string | null {\n if (typeof templateRelPath !== \"string\" || templateRelPath.length === 0) return null;\n if (path.isAbsolute(templateRelPath)) return null;\n const normalized = path.normalize(templateRelPath);\n if (normalized.startsWith(\"..\") || normalized.includes(`${path.sep}..${path.sep}`)) return null;\n const resolved = path.resolve(skillDir, normalized);\n if (!isContainedInRoot(resolved, skillDir)) return null;\n return resolved;\n}\n","// Pure paging/projection primitives shared by the store implementations.\n// Split out of store.ts so a backend module (e.g. sqliteStore.ts, which\n// store.ts imports to register its factory) can use them without a\n// runtime import cycle. store.ts re-exports everything here, so the\n// public `@mulmoclaude/core/collection/server` surface is unchanged.\n\nimport type { CollectionItem } from \"../core/schema\";\nimport { projectRecordFields } from \"../core/project\";\n\n/** Options for `page`. STORED fields only — computed fields (`derived` /\n * `toggle` / `embed` / rollups) never reach the store; project them at the\n * engine level after enrichment. */\nexport interface ListOptions {\n /** 0-based offset into the store's stable order (see the contract). */\n offset?: number;\n /** Max records returned. Absent = all remaining (subject to store caps). */\n limit?: number;\n /** Keep only these fields per record (the primary key is always kept). */\n fields?: readonly string[];\n}\n\nexport interface ListPage {\n items: CollectionItem[];\n /** Records in the collection before offset/limit — a lower bound when\n * `truncated` (the store capped its scan, e.g. `MAX_CSV_ROWS`). */\n total: number;\n truncated: boolean;\n}\n\nexport interface WriteOptions {\n /** Create semantics: fail with `kind: \"conflict\"` when the record\n * already exists (an O_EXCL open in the file store — race-safe). */\n refuseOverwrite?: boolean;\n}\n\n/** Project `fields` (+ the primary key, always) out of each record. Thin\n * server-typed alias over the shared isomorphic `projectRecordFields`\n * (../core/project.ts) — kept as the store layer's exported name. */\nexport function projectItemFields(items: CollectionItem[], fields: readonly string[] | undefined, primaryKey: string): CollectionItem[] {\n return projectRecordFields(items, fields, primaryKey);\n}\n\n/** Slice + project an already-ordered full read into a `ListPage` — the\n * shared emulation for stores without native paging. Pure, exported for\n * tests. `limit: 0` is a valid \"count only\" page. */\nexport function pageFromFullRead(items: CollectionItem[], opts: ListOptions, primaryKey: string, truncated: boolean): ListPage {\n const offset = Math.max(0, opts.offset ?? 0);\n const end = opts.limit === undefined ? items.length : offset + Math.max(0, opts.limit);\n return { items: projectItemFields(items.slice(offset, end), opts.fields, primaryKey), total: items.length, truncated };\n}\n","// The Firestore store: a SHARED collection's records as Firestore documents.\n//\n// Documents live at `apps/{aid}/collections/{cid}/items/{id}`. The identity is\n// `(aid, cid)`: `aid` comes from the repository's committed `app.json` and is\n// resolved once by discovery (`LoadedCollection.appId`), `cid` is always the\n// collection's slug. Nothing here reads a file or a session to work out WHERE —\n// it is handed a settled identity and builds the path from it.\n//\n// WHAT PROTECTS THESE DOCUMENTS. Not the shape of the path. An earlier draft of\n// this backend wrote under `users/{uid}/…` and leaned on the deployed rule\n// `users/{uid}/{document=**}`, so \"the schema cannot name a path\" WAS the\n// safety argument. It no longer is, and reading it that way would be a lie: an\n// `aid` is committed in a repository that anyone with a clone can edit. What\n// authorizes a read or a write is the app's MEMBER ROSTER — the rules resolve\n// `request.auth.token.email` against `apps/{aid}.members` and derive a role per\n// collection. Pointing at another app's `aid` is not an escape; it is a request\n// that gets refused, by name, for a caller who is not on that roster.\n//\n// The schema still declares no path, but for a different reason: there is\n// nothing for it to say. `aid` is one per app (four collections share one\n// roster), and `cid` is the slug. See `StorageZ`'s firestore arm.\n//\n// Availability: the authenticated handle belongs to the host's remote-host\n// session, so a shared collection is readable/writable only while that session\n// is open. This follows sqliteStore's precedent for an unavailable engine — the\n// FACTORY never throws (`storeFor` is called from ontology/validate/routes and\n// must not break unrelated screens), each METHOD fails with an actionable\n// message instead. It must never degrade to an empty result: \"no records\" and\n// \"not connected\" have to stay distinguishable, or a disconnected session looks\n// like data loss.\n//\n// SDK access goes through the `FirestoreDocs` seam (firestoreDocs.ts), not the\n// modular functions directly — that is what makes the backend testable without\n// a live Firestore.\n//\n// No `query`: there is no Firestore analogue of the DuckDB aggregation the CSV\n// store exposes. Absent `query` is a supported state — the engine-level\n// fallback (`runCollectionQuery`) answers aggregations instead.\n\nimport { isRecord } from \"@mulmoclaude/common\";\nimport { sharedCollectionKey, type SharedCollectionKey } from \"../core/collectionKey\";\nimport type { CollectionItem } from \"../core/schema\";\nimport { BackendUnavailableError } from \"./backendAvailability\";\nimport type { LoadedCollection } from \"./discoveredCollection\";\nimport { backoffDelayMs, classifyListenerError } from \"../../firestore/listen\";\nimport { firestoreHandle, log, publishCollectionChange, sharedCollectionChangePayload, type FirestoreHandle } from \"./host\";\nimport type { DeleteItemResult, IoOptions, WriteItemResult } from \"./io\";\nimport { safeRecordId } from \"./paths\";\nimport { projectItemFields, type ListOptions, type ListPage, type WriteOptions } from \"./storePage\";\nimport type { CollectionStore, StoreChangeListener, StoreUnsubscribe } from \"./store\";\n\n/** What every operation throws when there is no live session. Worded as an\n * instruction because it surfaces straight to the user and the agent. */\nconst NOT_CONNECTED =\n \"shared collection unavailable: connect remote-host first — these records live in the app's Firestore, not in the workspace, so nothing can be read or written while the session is closed\";\n\n/** What a schema declaring `storage.type: \"firestore\"` must have had resolved\n * for it before it can be served. Its absence is a programming error here, not\n * a user-facing state: discovery REFUSES such a schema when the repository\n * declares no `aid`, so a collection that reached this store has one. */\nconst NO_APP = \"shared collection has no app id — discovery should have refused this schema; check that the repository's app.json declares an `aid`\";\n\n/** The records subcollection of one shared collection.\n *\n * Takes a KEY, never loose strings, and the key is the only way to reach this\n * function. `sharedCollectionKey` is where the name rule lives (the charset a\n * Firestore document id, a pubsub channel segment and the completion-bell id\n * must all survive), so building a path cannot be a way around it. */\nexport function sharedItemsPath(key: SharedCollectionKey): string {\n return `apps/${key.aid}/collections/${key.cid}/items`;\n}\n\n/** The collection's identity, from what discovery resolved. Throws on a\n * missing `appId` — see NO_APP. */\nfunction keyOf(collection: Pick<LoadedCollection, \"slug\" | \"appId\">): SharedCollectionKey {\n if (collection.appId === undefined) throw new Error(NO_APP);\n // `cid` IS the slug. Fixed here, deliberately, rather than made configurable:\n // the schema, the views and the skill text sit in a directory named by the\n // slug, so a second name would need a mapping table between what a collection\n // is called on disk and what it is called in its app — two names for one\n // thing, which is the exact collision `CollectionKey` exists to remove.\n return sharedCollectionKey(collection.appId, collection.slug);\n}\n\nfunction requireHandle(): FirestoreHandle {\n const handle = firestoreHandle();\n if (handle === null) throw new BackendUnavailableError(NOT_CONNECTED);\n return handle;\n}\n\n/** Firestore's own refusal, named.\n *\n * `permission-denied` is the failure a shared collection has most often and\n * the one the SDK explains worst (\"Missing or insufficient permissions\") — it\n * says nothing about WHO was refused, which is the only fact that leads to a\n * fix. Authorization here is the app's member roster, keyed by email, so the\n * signed-in address is what the app's owner needs in order to add it. This is\n * the whole reason `FirestoreHandle` carries `email`.\n *\n * Reported as a `BackendUnavailableError` deliberately, even though it is a\n * refusal rather than an outage: the layers above catch broadly, and without a\n * type to test, `store.read(...).catch(() => null)` reports \"record missing\"\n * and an ontology count reports 0 — a denial would read as an empty\n * collection, which is the exact confusion this backend refuses to create. */\nfunction isPermissionDenied(err: unknown): boolean {\n return isRecord(err) && err.code === \"permission-denied\";\n}\n\nfunction deniedMessage(key: SharedCollectionKey, email: string): string {\n return `permission denied on shared collection '${key.cid}' of app '${key.aid}' — signed in as ${email}. A shared collection is authorized by the app's member roster (by email), so this address needs a role for '${key.cid}' (or '*'); only the app's owner can add it.`;\n}\n\n/** Run one SDK call, translating a roster denial. Every read and write goes\n * through this — a denial reaching one path and not another would mean the\n * message a user sees depends on which screen they were on. */\nasync function guarded<T>(key: SharedCollectionKey, email: string, run: () => Promise<T>): Promise<T> {\n try {\n return await run();\n } catch (err) {\n if (!isPermissionDenied(err)) throw err;\n throw new BackendUnavailableError(deniedMessage(key, email));\n }\n}\n\n/** A stored document's fields → a record. A document written by hand (or by an\n * older version) can hold anything, so a non-object is dropped rather than\n * surfaced as a broken record — the same fail-soft the file store applies to\n * an unparseable `.json`. */\n/** A stored document as a record — with its identity taken from the DOCUMENT\n * ID, never from the document's own fields.\n *\n * This is the one place a shared record's identity is decided, and it is\n * decided against the field the writer supplied on purpose.\n *\n * The rules can constrain the document id (`idFrom` pins it to the\n * submitter's uid, or to uid+field) and they CANNOT constrain the value of a\n * field: `validateOk` checks which keys are present, `keyFieldsOk` checks a\n * declared enum, and nothing compares `request.resource.data[primaryKey]`\n * with the path being written. So a public submitter writing at their one\n * permitted document id could put ANY value in the primary-key field —\n * another member's record id, or a duplicate — and every reader would take it\n * as the record's identity. Overwriting it here makes that unreachable rather\n * than merely discouraged, and it costs nothing: for a record written through\n * this store the two already agree, because `firestoreWrite` writes at the id\n * it was given.\n *\n * It also removes the reason a submit path would have had to accept the\n * primary key as a `createField` at all — a submission that cannot name its\n * own id is exactly right when the id is the thing being assigned. */\nfunction toItem(data: unknown, docId: string, primaryKey: string): CollectionItem | null {\n return isRecord(data) ? { ...data, [primaryKey]: docId } : null;\n}\n\n/** Record ids are validated with the SAME helper every other backend uses.\n * Firestore would accept ids the file store refuses, but a record should stay\n * portable between backends — and an id that can't round-trip to a filename\n * would break an export back to a file collection. */\nfunction withSafeId<T>(itemId: string, onInvalid: () => T, run: (safeId: string, handle: FirestoreHandle) => T): T {\n const safeId = safeRecordId(itemId);\n if (safeId === null) return onInvalid();\n return run(safeId, requireHandle());\n}\n\nasync function firestoreList(key: SharedCollectionKey, primaryKey: string): Promise<CollectionItem[]> {\n const { docs, email } = requireHandle();\n const entries = await guarded(key, email, () => docs.list(sharedItemsPath(key)));\n return entries.map((entry) => toItem(entry.data, entry.id, primaryKey)).filter((item): item is CollectionItem => item !== null);\n}\n\n/** Paging is emulated over a full ordered read rather than pushed into\n * Firestore: `offset` has no server-side form there (the cursor API needs the\n * preceding document, which a stateless offset/limit call doesn't have), and\n * `total` needs the full count anyway. Hence `nativePaging: false` — the\n * capability is honest about the cost. */\nasync function firestorePage(key: SharedCollectionKey, primaryKey: string, opts: ListOptions): Promise<ListPage> {\n const items = await firestoreList(key, primaryKey);\n const offset = Math.max(0, opts.offset ?? 0);\n const sliced = opts.limit === undefined ? items.slice(offset) : items.slice(offset, offset + Math.max(0, opts.limit));\n return { items: projectItemFields(sliced, opts.fields, primaryKey), total: items.length, truncated: false };\n}\n\nasync function firestoreRead(key: SharedCollectionKey, itemId: string, primaryKey: string): Promise<CollectionItem | null> {\n return withSafeId(\n itemId,\n () => Promise.resolve(null),\n async (safeId, { docs, email }) => toItem(await guarded(key, email, () => docs.get(sharedItemsPath(key), safeId)), safeId, primaryKey),\n );\n}\n\n/** Publish the \"records changed\" ping for a shared collection.\n *\n * `sharedCollectionChangePayload` NEVER stamps a root, and that matters beyond\n * tidiness: this payload is relayed to the browser and on into an\n * LLM-generated custom-view iframe, so a filesystem path on it would be a\n * disclosure. The type makes it unreachable rather than trusting the caller. */\nfunction publishShared(key: SharedCollectionKey, ids: string[], operation: \"upsert\" | \"delete\"): void {\n publishCollectionChange(sharedCollectionChangePayload({ slug: key.cid, ids, op: operation }, key.aid));\n}\n\nasync function firestoreWrite(\n key: SharedCollectionKey,\n itemId: string,\n item: CollectionItem,\n opts: IoOptions & { refuseOverwrite?: boolean | undefined },\n): Promise<WriteItemResult> {\n return withSafeId<Promise<WriteItemResult>>(\n itemId,\n () => Promise.resolve({ kind: \"invalid-id\", itemId }),\n async (safeId, { docs, email }) => {\n const collectionPath = sharedItemsPath(key);\n if (opts.refuseOverwrite) {\n const created = await guarded(key, email, () => docs.create(collectionPath, safeId, item));\n if (!created) return { kind: \"conflict\", itemId: safeId };\n } else {\n await guarded(key, email, () => docs.set(collectionPath, safeId, item));\n }\n if (opts.slug) publishShared(key, [safeId], \"upsert\");\n return { kind: \"ok\", itemId: safeId, item };\n },\n );\n}\n\nasync function firestoreDelete(key: SharedCollectionKey, itemId: string, opts: IoOptions): Promise<DeleteItemResult> {\n return withSafeId<Promise<DeleteItemResult>>(\n itemId,\n () => Promise.resolve({ kind: \"invalid-id\", itemId }),\n async (safeId, { docs, email }) => {\n const removed = await guarded(key, email, () => docs.delete(sharedItemsPath(key), safeId));\n if (!removed) return { kind: \"not-found\", itemId: safeId };\n if (opts.slug) publishShared(key, [safeId], \"delete\");\n return { kind: \"ok\", itemId: safeId };\n },\n );\n}\n\n// --- live updates -----------------------------------------------------------\n\n/** One live subscription to a shared collection's records. */\ninterface SharedWatch {\n key: SharedCollectionKey;\n onChange: StoreChangeListener;\n stopped: boolean;\n detach: () => void;\n retryTimer: ReturnType<typeof setTimeout> | null;\n attempt: number;\n}\n\n/** Deliver one snapshot's changes.\n *\n * THE FIRST SNAPSHOT IS ONE COLLECTION-LEVEL REPORT, not N per-record ones.\n * `onSnapshot` hands over the collection's current contents immediately, as a\n * snapshot in which every existing document reads as `added`.\n *\n * Dropping it is wrong. There is a GAP on either side of a subscription — the\n * boot reconcile finishes before the listener arms, and a listener that died\n * is re-armed after a backoff — and a record that moved inside one of those\n * gaps appears only in that first snapshot. Since this backend now HAS a\n * `watch`, the watcher's periodic re-derivation no longer covers it, so a\n * dropped first snapshot means stale bells and stale views until that record\n * happens to change again. That is exactly the failure this step was supposed\n * to remove.\n *\n * Announcing every record individually is also wrong: it is a refresh storm\n * to every open view, on every mount and every reconnect. `{ kind: \"collection\" }`\n * says the same thing in one event — the watcher answers it with a full\n * re-derivation plus a sweep, which is precisely \"work out what changed while\n * I was not listening\".\n *\n * A change this process made itself also arrives here — the write path has\n * already published for it, so the record is reconciled twice. Harmless\n * (reconciling is idempotent) and left alone: suppressing it would mean\n * tracking our own in-flight writes, and getting that wrong loses a real\n * change rather than a duplicate one. */\nfunction deliverSnapshot(run: SharedWatch, ids: string[], initial: boolean): void {\n if (run.stopped) return;\n if (initial) {\n run.onChange({ kind: \"collection\" });\n return;\n }\n for (const itemId of ids) run.onChange({ kind: \"item\", itemId });\n}\n\n/** A listener died. Firestore never revives one on its own, so the choice is\n * re-subscribe or go dark.\n *\n * NO OVERALL RETRY WINDOW, unlike `hostRunner`'s listener. There, giving up\n * escalates to a lifecycle owner that can re-authenticate; here there is\n * nobody above to escalate to — the watcher registers a mounted collection\n * once and never re-arms it, so \"give up\" means this collection serves stale\n * data until the server restarts. A capped 30s re-subscribe is cheap and\n * makes recovery automatic when the session comes back.\n *\n * A FATAL error still stops: `permission-denied` is what a revoked membership\n * looks like, and re-listening cannot restore a grant. It is logged as such\n * rather than retried in silence. */\nfunction handleWatchError(run: SharedWatch, error: unknown): void {\n if (run.stopped) return;\n if (classifyListenerError(error) === \"fatal\") {\n log.warn(\"collections\", \"shared collection listener stopped\", {\n aid: run.key.aid,\n cid: run.key.cid,\n error: isRecord(error) && typeof error.message === \"string\" ? error.message : String(error),\n detail: \"live updates for this collection are off until the server re-syncs; a revoked membership looks exactly like this\",\n });\n return;\n }\n run.retryTimer = setTimeout(() => subscribeShared(run), backoffDelayMs(run.attempt));\n run.attempt += 1;\n}\n\n/** (Re-)arm the listener. The handle is fetched HERE rather than captured: a\n * retry may land after the session was replaced, and listening through a\n * closed session's handle would fail forever. */\nfunction subscribeShared(run: SharedWatch): void {\n run.retryTimer = null;\n if (run.stopped) return;\n const handle = firestoreHandle();\n if (handle === null) {\n // Not connected yet. The steady state while remote-host is closed, so it\n // is a wait rather than an error — no log, capped backoff, and the next\n // attempt picks up the session the moment it opens.\n run.retryTimer = setTimeout(() => subscribeShared(run), backoffDelayMs(run.attempt));\n run.attempt += 1;\n return;\n }\n run.detach = handle.docs.watch(\n sharedItemsPath(run.key),\n (ids, meta) => {\n // A healthy snapshot proves the listener recovered: the ladder starts\n // fresh for whatever comes next.\n run.attempt = 0;\n deliverSnapshot(run, ids, meta.initial);\n },\n (error) => handleWatchError(run, error),\n );\n}\n\n/** Subscribe to a shared collection's records.\n *\n * Returns `null` when there is no session: the watcher reads that as \"has a\n * watch but could not arm it\", leaves the collection unmounted and retries on\n * the next sync tick — which is the right channel for a connection that has\n * not happened yet. Arming a dead listener instead would mark the slug mounted\n * forever. */\nfunction armSharedWatch(key: SharedCollectionKey, onChange: StoreChangeListener): StoreUnsubscribe | null {\n if (firestoreHandle() === null) return null;\n const run: SharedWatch = { key, onChange, stopped: false, detach: () => {}, retryTimer: null, attempt: 0 };\n subscribeShared(run);\n return () => {\n run.stopped = true;\n if (run.retryTimer !== null) clearTimeout(run.retryTimer);\n run.detach();\n };\n}\n\n/** The store factory registered for `storage.type === \"firestore\"`.\n * Synchronous and connection-agnostic by contract — see the header. */\nexport function firestoreStoreFor(collection: LoadedCollection, opts: IoOptions): CollectionStore {\n const { primaryKey } = collection.schema;\n const ioOpts: IoOptions = { ...opts, slug: opts.slug ?? collection.slug };\n // Every method is `async` and resolves the key INSIDE itself, so a bad\n // identity rejects the one call instead of throwing out of the factory. The\n // factory is called from ontology / validate / route handlers that list many\n // collections; one that throws there takes an unrelated screen down with it.\n return {\n capabilities: { writable: true, nativeQuery: false, nativePaging: false },\n list: async () => firestoreList(keyOf(collection), primaryKey),\n page: async (pageOpts = {}) => firestorePage(keyOf(collection), primaryKey, pageOpts),\n read: async (itemId: string) => firestoreRead(keyOf(collection), itemId, primaryKey),\n write: async (itemId: string, item: CollectionItem, writeOpts: WriteOptions = {}) =>\n firestoreWrite(keyOf(collection), itemId, item, { ...ioOpts, refuseOverwrite: writeOpts.refuseOverwrite }),\n delete: async (itemId: string) => firestoreDelete(keyOf(collection), itemId, ioOpts),\n // Having a `watch` at all is what takes this backend out of the watcher's\n // clock-tick fallback: `cannotReportChanges()` asks the store whether it\n // can report, never which backend it is, so this one line is the whole\n // change on that side.\n watch: async (onChange) => armSharedWatch(keyOf(collection), onChange),\n };\n}\n","// The repository's app declaration — `<root>/app.json` — and the one field\n// this step reads from it: `aid`.\n//\n// WHY THE REPOSITORY AND NOT A BINDING. A shared collection's identity is\n// `(aid, cid)`, and `aid` is COMMITTED, so every clone of the repository\n// resolves the same app and an invitation is about authorization rather than\n// discovery. That makes `aid` a property of the collection's LOCATION, exactly\n// like `storage.path` — not a property of the session. A host binding would\n// make it a process global, which is wrong the moment one server process serves\n// several project roots (MulmoTerminal does), because then every repository's\n// collections would point at one app.\n//\n// WHY NOT THE SCHEMA. The unit of sharing is the app, not the collection: four\n// collections share one member roster and one public config, so `aid` sits once\n// per repository rather than once per schema. A per-collection `aid` would also\n// be a second place to change it, and the two would drift.\n//\n// AUTHORED, NOT PUBLISHED. This is the file a human (or the agent) writes. The\n// Firestore document at `apps/{aid}` is a DIFFERENT thing that `publish`\n// derives from it — flattened for the security rules to read, with epoch-millis\n// windows and a derived `memberEmails`. Do not read this file as if it were\n// that one, and do not write this file from anything that publishes.\n//\n// SCOPE. Only `aid` is read here. `members` / `public` belong to `publish`,\n// which does not exist yet. `aidEnv` (a per-worktree app id, so a feature\n// branch cannot mutate the team's live records) also does not: it needs the\n// host to resolve a worktree variable that lives in MulmoTerminal's session\n// environment and NOT in this process's `process.env`, so it arrives as a host\n// resolver hook rather than a direct env read. Both land later; the point of\n// funnelling every read through this one function is that they land HERE and\n// nowhere else.\n\nimport { readFileSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { isErrorWithCode, isRecord } from \"@mulmoclaude/common\";\nimport { isValidCollectionName } from \"../core/collectionKey\";\n\n/** The app declaration's filename, at the repository root. */\nexport const APP_MANIFEST_FILE = \"app.json\";\n\n/** What this step reads out of `app.json`. Deliberately one field: every key\n * added here is a key `publish` and this loader could disagree about. */\nexport interface AppManifest {\n /** The app id — the `{aid}` in `apps/{aid}/collections/{cid}/items`. */\n aid: string;\n}\n\n/** Why a root has no usable `aid`. Returned rather than thrown because the\n * caller is an acceptance gate whose whole job is to turn this into a\n * one-line reason a collection was skipped. */\nexport type AppManifestFailure = { kind: \"missing\" } | { kind: \"unreadable\"; detail: string } | { kind: \"malformed\"; detail: string };\n\nexport type AppManifestResult = { ok: true; manifest: AppManifest } | ({ ok: false } & AppManifestFailure);\n\n/** Read `<root>/app.json` and return its `aid`.\n *\n * SYNCHRONOUS on purpose. The caller is `acceptParsedSchema`, which is sync\n * and is shared by discovery and `manageCollection`'s `putSchema` precisely so\n * that a schema which would be skipped on the next discovery cannot be written\n * as if it were fine. Making this async would split that gate in two, and the\n * half that lost the check is the half the author sees. The file is a few\n * hundred bytes and is read once per firestore collection per discovery pass.\n *\n * Never cached. `app.json` is edited by hand and by the agent, and a cache\n * here would mean the app a collection points at is whatever it was when the\n * server started. */\nexport function loadAppManifest(root: string): AppManifestResult {\n let raw: string;\n try {\n raw = readFileSync(path.join(root, APP_MANIFEST_FILE), \"utf-8\");\n } catch (err) {\n if (isErrorWithCode(err) && err.code === \"ENOENT\") return { ok: false, kind: \"missing\" };\n return { ok: false, kind: \"unreadable\", detail: String(err) };\n }\n return parseAppManifest(raw);\n}\n\n/** The parse half, exported so it can be tested without a filesystem.\n *\n * `aid` is validated with `isValidCollectionName` — the SAME predicate the\n * `CollectionKey` constructors apply — rather than a rule of its own. An `aid`\n * is re-encoded downstream as a Firestore document id, a pubsub channel\n * segment and a cache key, each with a different character that would break\n * it; one rule, stated once, is what keeps those layers from disagreeing.\n * Rejecting here rather than at `sharedCollectionKey` only changes WHERE the\n * author is told: a reason on the collection they wrote, instead of a throw\n * from inside a store call. */\nexport function parseAppManifest(raw: string): AppManifestResult {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n return { ok: false, kind: \"malformed\", detail: `not valid JSON (${String(err)})` };\n }\n if (!isRecord(parsed)) return { ok: false, kind: \"malformed\", detail: \"is not a JSON object\" };\n const { aid } = parsed;\n if (typeof aid !== \"string\" || aid.length === 0) return { ok: false, kind: \"malformed\", detail: \"declares no `aid` string\" };\n if (!isValidCollectionName(aid)) return { ok: false, kind: \"malformed\", detail: `\\`aid\\` '${aid}' is not a valid app id` };\n return { ok: true, manifest: { aid } };\n}\n\n/** The failure as the one line an author can act on. Kept next to the failure\n * type so a new variant cannot be added without wording it. */\nexport function appManifestReason(failure: AppManifestFailure, root: string): string {\n const manifestPath = path.join(root, APP_MANIFEST_FILE);\n if (failure.kind === \"missing\") return `a shared collection needs an app: create ${manifestPath} declaring an \\`aid\\``;\n if (failure.kind === \"unreadable\") return `cannot read ${manifestPath}: ${failure.detail}`;\n return `${manifestPath} ${failure.detail}`;\n}\n","// Read / write item files for schema-driven collections. Records live at\n// `<dataDir>/<itemId>.json`, one JSON object per file. Writes are\n// atomic; deletes are idempotent enough to expose a clear 404 when\n// the file is missing.\n\nimport { lstat, mkdir, open, readdir, readFile, unlink } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { collectionChangePayload, getWorkspaceRoot, log, publishCollectionChange } from \"./host\";\nimport { writeFileAtomic } from \"../../files/atomic.js\";\nimport { newItemId } from \"../core/itemId\";\nimport { isContainedInRoot, itemFilePath, safeRecordId } from \"./paths\";\nimport type { CollectionItem, CollectionSchema } from \"../core/schema\";\nimport { isErrorWithCode, isRecord } from \"@mulmoclaude/common\";\n\nexport interface IoOptions {\n /** Override the workspace root for containment checks. Default:\n * the live `workspacePath`. Tests point this at a `mkdtempSync`\n * tree so the realpath-based escape detection can be exercised\n * without touching `~/mulmoclaude/`. Same pattern as\n * `server/workspace/skills/catalog.ts#CatalogOptions`. */\n workspaceRoot?: string | undefined;\n /** Collection slug this write/delete belongs to. When provided, a\n * successful write/delete publishes a record-change event (see\n * `publishCollectionChange`) so live views refetch. `writeItem` has no\n * slug of its own (it's keyed by `dataDir`), so callers thread it through;\n * omitting it just means no event is published (internal / test writes). */\n slug?: string | undefined;\n}\n\n/** True iff `filePath` exists and is a regular file (NOT a symlink).\n * Defends `listItems` / `readItem` against `*.json` symlinks placed\n * inside an otherwise-contained data dir — without this, a record\n * file could symlink to /etc/passwd and the detail endpoint would\n * happily serve it. Returns false on ENOENT and on any other lstat\n * failure so the caller's \"missing\" branch covers those cases too.\n * Exported so `ontology.ts`'s record COUNT classifies entries with the\n * SAME lstat logic — the two must agree on what a record file is. */\nexport async function isRegularFile(filePath: string): Promise<boolean> {\n try {\n const info = await lstat(filePath);\n return info.isFile();\n } catch {\n return false;\n }\n}\n\n/** Read one JSON record file. Returns null when the file is missing,\n * is a symlink (file-disclosure defense), parses to a non-object,\n * or has a read/parse error. Caller logs the per-entry skip — this\n * helper just classifies. Split out to keep `listItems` under the\n * `sonarjs/cognitive-complexity` threshold. */\n/** Parse a record file's text into a plain-object `CollectionItem`, or\n * null when it isn't a JSON object (array / scalar / null). */\nfunction parseRecordJson(raw: string): CollectionItem | null {\n const parsed: unknown = JSON.parse(raw);\n return isRecord(parsed) ? parsed : null;\n}\n\nasync function tryReadRecord(filePath: string): Promise<CollectionItem | null> {\n if (!(await isRegularFile(filePath))) return null;\n try {\n return parseRecordJson(await readFile(filePath, \"utf-8\"));\n } catch {\n return null;\n }\n}\n\n/** Read every record under `dataDir`. Returns [] if the dir doesn't\n * exist yet (legitimate first-use state). Malformed JSON files and\n * symlinked records are skipped (the latter is a file-disclosure\n * defense — see `isRegularFile`). Re-validates the realpath\n * containment to defend against a symlinked data dir appearing\n * between discovery and use. */\nexport async function listItems(dataDir: string, opts: IoOptions = {}): Promise<CollectionItem[]> {\n const workspaceRoot = opts.workspaceRoot ?? getWorkspaceRoot();\n if (!isContainedInRoot(dataDir, workspaceRoot)) {\n log.warn(\"collections\", \"listItems refused: dataDir escapes workspace via symlink\", { dataDir });\n return [];\n }\n let entries: string[];\n try {\n entries = await readdir(dataDir);\n } catch (err) {\n if (isErrorWithCode(err) && err.code === \"ENOENT\") return [];\n throw err;\n }\n const results: CollectionItem[] = [];\n for (const name of entries) {\n if (!name.endsWith(\".json\")) continue;\n if (name.startsWith(\".\")) continue;\n const filePath = path.join(dataDir, name);\n const record = await tryReadRecord(filePath);\n if (record === null) {\n log.warn(\"collections\", \"skipping record (missing, symlink, or unreadable)\", { path: filePath });\n continue;\n }\n results.push(record);\n }\n return results;\n}\n\n/** Read one record by id. Returns null when the file is missing,\n * when the resolved path escapes the workspace via a symlink, or\n * when the record file itself is a symlink (file-disclosure\n * defense — see `isRegularFile`). */\nexport async function readItem(dataDir: string, itemId: string, opts: IoOptions = {}): Promise<CollectionItem | null> {\n const safeId = safeRecordId(itemId);\n if (safeId === null) return null;\n const workspaceRoot = opts.workspaceRoot ?? getWorkspaceRoot();\n if (!isContainedInRoot(dataDir, workspaceRoot)) return null;\n const filePath = itemFilePath(dataDir, safeId);\n if (!(await isRegularFile(filePath))) return null;\n try {\n return parseRecordJson(await readFile(filePath, \"utf-8\"));\n } catch (err) {\n if (isErrorWithCode(err) && err.code === \"ENOENT\") return null;\n throw err;\n }\n}\n\nexport interface WriteItemOptions extends IoOptions {\n /** When true (POST/create), refuse to overwrite an existing file\n * and return `kind: \"conflict\"`. Update flow (PUT) leaves it false. */\n refuseOverwrite?: boolean | undefined;\n}\n\nexport type WriteItemResult =\n | { kind: \"ok\"; itemId: string; item: CollectionItem }\n | { kind: \"invalid-id\"; itemId: string }\n | { kind: \"conflict\"; itemId: string }\n | { kind: \"path-escape\"; itemId: string };\n\n/** The symlink-containment refusal every record path shares: one check, one\n * warn, one answer. Extracted because this is a security RULE applied at\n * three sites (write pre-mkdir, write post-mkdir, delete) — a fix to the\n * check must not be able to land at only one of them.\n *\n * `stage` names the call site so the warn stays as diagnosable as the three\n * hand-written copies were.\n *\n * Scope, stated explicitly because a reviewer asks every time: this catches\n * a symlink that EXISTS when we look — `isContainedInRoot` realpaths the\n * closest existing ancestor, so a pre-planted escape is refused. It does not\n * and cannot close the check-then-use race, where an ancestor is swapped for\n * a symlink between this call and the `mkdir` / `open` / `unlink` that\n * follows. Closing that needs directory-handle I/O anchored at the workspace\n * (`openat` + `O_NOFOLLOW`), which `node:fs` does not expose — it would mean\n * a different I/O layer, not a tighter check here.\n *\n * That race is deliberately outside this app's threat model: the process is\n * loopback-bound and bearer-authed, so anyone able to swap directories inside\n * the workspace is already the workspace owner — the same trust principal the\n * writes belong to. Revisit if collections ever serve a lower-trust caller. */\nfunction escapesWorkspace(dataDir: string, workspaceRoot: string, itemId: string, stage: string): boolean {\n if (isContainedInRoot(dataDir, workspaceRoot)) return false;\n log.warn(\"collections\", `${stage} refused: dataDir escapes workspace via symlink`, { dataDir, itemId });\n return true;\n}\n\n/** Write a record. Ensures the directory exists, validates the id,\n * re-checks symlink containment after mkdir, and writes atomically.\n *\n * Create path (`refuseOverwrite: true`) uses an O_EXCL `wx` open\n * rather than `stat` + `writeFileAtomic` to close a check-then-write\n * race: two concurrent POSTs would otherwise both pass the existence\n * check and one would silently overwrite the other. The trade-off\n * is that the create path is not crash-atomic (a partial file could\n * remain if the process dies mid-write); acceptable here because\n * records are small JSON blobs and the next read either parses or\n * is skipped via the \"malformed JSON\" branch in `listItems`.\n *\n * Update path (`refuseOverwrite: false`) uses `writeFileAtomic` so\n * PUT remains crash-atomic. No race there — the URL pins the id. */\nexport async function writeItem(dataDir: string, itemId: string, item: CollectionItem, opts: WriteItemOptions = {}): Promise<WriteItemResult> {\n const safeId = safeRecordId(itemId);\n if (safeId === null) return { kind: \"invalid-id\", itemId };\n const workspaceRoot = opts.workspaceRoot ?? getWorkspaceRoot();\n // Containment check runs BEFORE mkdir so we never create\n // directories outside the workspace even if a symlink ancestor\n // was swapped after discovery. We re-check AFTER mkdir to catch\n // a symlink racing in between the two — belt + suspenders, cheap\n // and the only honest defense against TOCTOU on directory creation.\n if (escapesWorkspace(dataDir, workspaceRoot, safeId, \"writeItem (pre-mkdir)\")) return { kind: \"path-escape\", itemId: safeId };\n await mkdir(dataDir, { recursive: true });\n if (escapesWorkspace(dataDir, workspaceRoot, safeId, \"writeItem (post-mkdir)\")) return { kind: \"path-escape\", itemId: safeId };\n const filePath = itemFilePath(dataDir, safeId);\n const payload = `${JSON.stringify(item, null, 2)}\\n`;\n\n if (opts.refuseOverwrite) {\n let handle;\n try {\n handle = await open(filePath, \"wx\");\n } catch (err) {\n if (isErrorWithCode(err) && err.code === \"EEXIST\") return { kind: \"conflict\", itemId: safeId };\n throw err;\n }\n try {\n await handle.writeFile(payload);\n } finally {\n await handle.close();\n }\n } else {\n await writeFileAtomic(filePath, payload);\n }\n // Publish AFTER the write lands so a live subscriber that refetches always\n // sees the new record (never a read-before-write race).\n if (opts.slug) publishCollectionChange(collectionChangePayload({ slug: opts.slug, ids: [safeId], op: \"upsert\" }, opts.workspaceRoot));\n return { kind: \"ok\", itemId: safeId, item };\n}\n\nexport type DeleteItemResult =\n { kind: \"ok\"; itemId: string } | { kind: \"invalid-id\"; itemId: string } | { kind: \"not-found\"; itemId: string } | { kind: \"path-escape\"; itemId: string };\n\nexport async function deleteItem(dataDir: string, itemId: string, opts: IoOptions = {}): Promise<DeleteItemResult> {\n const safeId = safeRecordId(itemId);\n if (safeId === null) return { kind: \"invalid-id\", itemId };\n const workspaceRoot = opts.workspaceRoot ?? getWorkspaceRoot();\n if (escapesWorkspace(dataDir, workspaceRoot, safeId, \"deleteItem\")) return { kind: \"path-escape\", itemId: safeId };\n const filePath = itemFilePath(dataDir, safeId);\n try {\n await unlink(filePath);\n if (opts.slug) publishCollectionChange(collectionChangePayload({ slug: opts.slug, ids: [safeId], op: \"delete\" }, opts.workspaceRoot));\n return { kind: \"ok\", itemId: safeId };\n } catch (err) {\n if (isErrorWithCode(err) && err.code === \"ENOENT\") return { kind: \"not-found\", itemId: safeId };\n throw err;\n }\n}\n\n/** Generate a record id. Used by POST when the form doesn't carry a\n * primary-key value (UI shortcut — Claude normally derives a semantic\n * id from the record's name). Delegates to the isomorphic generator, so\n * the id the UI pre-fills and the id a blank-id POST mints stay the\n * same thing rather than two implementations that have to agree. */\nexport function generateItemId(): string {\n return newItemId();\n}\n\n/** The item id a CREATE should use for `schema`, or null when the\n * caller should generate one. A singleton collection pins every\n * create to its fixed `schema.singleton` id, so the \"at most one\n * record\" contract is enforced server-side (a second create targets\n * the same file and hits `writeItem`'s refuseOverwrite conflict) —\n * not only in the UI. Otherwise the record's own primaryKey value\n * wins, falling back to a generated id (null = \"generate\"). */\nexport function resolveCreateItemId(schema: CollectionSchema, record: CollectionItem): string | null {\n if (schema.singleton) return schema.singleton;\n const primaryRaw = record[schema.primaryKey];\n return typeof primaryRaw === \"string\" && primaryRaw.length > 0 ? primaryRaw : null;\n}\n","// The aggregation-query DSL for `dataSource` collections (v2 of\n// plans/done/feat-collection-csv-duckdb-source.md). A structured JSON query —\n// NEVER raw SQL — because SQL-the-language isn't scoped to the data file\n// (`read_csv`/`read_text`/`COPY TO` reach the whole filesystem) and the\n// query authors (custom views, the agent) are influenceable by untrusted\n// content. The DSL is safe by construction: column names / aliases become\n// quoted identifiers, every VALUE rides as a prepared-statement parameter,\n// and the shape can't express file access at all. Compiled to SQL\n// server-side (`collection/server/csvQuery.ts`).\n//\n// Isomorphic: custom views build these objects in the browser; the server\n// validates with the same zod schema before compiling.\n\nimport { z } from \"zod\";\n\n/** Result-column aliases double as SQL identifiers and JSON keys — keep\n * them to a conservative identifier charset so neither side needs\n * escaping gymnastics. */\nconst SAFE_ALIAS_PATTERN = /^[A-Za-z_]\\w{0,63}$/;\n\n/** Hard ceiling on returned rows; `limit` clamps below it. A group-by on\n * a near-unique column would otherwise return one row per source row —\n * the exact materialization the aggregate path exists to avoid. */\nexport const MAX_QUERY_ROWS = 10000;\n/** Default row cap when the query declares no `limit`. */\nexport const DEFAULT_QUERY_ROWS = 1000;\n/** Cap on aggregate expressions per query — a record shape has no\n * intrinsic size limit, and thousands of expressions would make one\n * full-file scan arbitrarily wide. */\nexport const MAX_QUERY_AGGREGATES = 32;\n\n/** One aggregate column: `count` (rows; `column` optional to count\n * non-null cells) or `sum`/`avg`/`min`/`max` over a named CSV column. */\nexport const QueryAggregateZ = z\n .object({\n op: z.enum([\"count\", \"sum\", \"avg\", \"min\", \"max\"]),\n column: z.string().min(1).optional(),\n })\n .refine((aggregate) => aggregate.op === \"count\" || aggregate.column !== undefined, {\n message: \"`column` is required for every aggregate op except `count`\",\n path: [\"column\"],\n });\n\n/** One filter condition. Same op vocabulary as the schema-level `where`\n * (`core/where.ts`) so authors learn one set; values may be typed\n * (number / boolean) since CSV columns are. `in` requires an array\n * value, every other op a scalar. */\nexport const QueryWhereZ = z\n .object({\n field: z.string().min(1),\n op: z.enum([\"eq\", \"ne\", \"in\", \"gt\", \"gte\", \"lt\", \"lte\", \"contains\"]),\n value: z.union([\n z.string(),\n z.number(),\n z.boolean(),\n z\n .array(z.union([z.string(), z.number(), z.boolean()]))\n .min(1)\n .max(100),\n ]),\n })\n .refine((cond) => (cond.op === \"in\") === Array.isArray(cond.value), {\n message: \"`in` requires an array value (the allowed set); every other op requires a scalar value\",\n path: [\"value\"],\n });\n\nexport const QueryOrderZ = z.object({\n /** A `groupBy` column or an aggregate alias — membership enforced by\n * the whole-query refine below. */\n field: z.string().min(1),\n dir: z.enum([\"asc\", \"desc\"]).optional(),\n});\n\n/** The whole query. At least one of `groupBy` / `aggregates` must be\n * present: bare `groupBy` is a DISTINCT listing, bare `aggregates` a\n * whole-file scalar row, together a grouped aggregation. */\nexport const CollectionQueryZ = z\n .object({\n // Case-insensitively unique: DuckDB treats `category` and `Category`\n // as the SAME identifier, groups once, and auto-renames the duplicate\n // SELECT column (`Category_1`) — silently breaking the result-key\n // contract. Same rule as the alias refine below.\n groupBy: z\n .array(z.string().min(1))\n .max(8)\n .refine((columns) => new Set(columns.map((column) => column.toLowerCase())).size === columns.length, {\n message: \"`groupBy` columns must be unique (case-insensitively — SQL identifiers ignore case)\",\n })\n .optional(),\n aggregates: z\n .record(z.string().regex(SAFE_ALIAS_PATTERN, \"aggregate aliases must be simple identifiers (letters/digits/underscore)\"), QueryAggregateZ)\n .optional(),\n where: z.array(QueryWhereZ).max(16).optional(),\n orderBy: z.array(QueryOrderZ).max(4).optional(),\n limit: z.number().int().min(1).max(MAX_QUERY_ROWS).optional(),\n })\n .refine((query) => (query.groupBy?.length ?? 0) > 0 || Object.keys(query.aggregates ?? {}).length > 0, {\n message: \"declare at least one of `groupBy` (columns to bucket by) or `aggregates` (values to compute)\",\n path: [\"groupBy\"],\n })\n .refine((query) => Object.keys(query.aggregates ?? {}).length <= MAX_QUERY_AGGREGATES, {\n message: `\\`aggregates\\` supports at most ${MAX_QUERY_AGGREGATES} entries`,\n path: [\"aggregates\"],\n })\n // An alias shadowing a groupBy column would make the SELECT list (and\n // the result object) ambiguous. CASE-INSENSITIVE: DuckDB treats\n // identifiers that differ only by case as the same name (quoting does\n // not opt out), so `Total` vs `total` collide there even though they\n // are distinct JSON keys here — reject both same-case and cross-case\n // collisions, including between two aliases.\n .refine(\n (query) => {\n const groupLower = new Set((query.groupBy ?? []).map((column) => column.toLowerCase()));\n const seen = new Set<string>();\n return Object.keys(query.aggregates ?? {}).every((alias) => {\n const lower = alias.toLowerCase();\n if (groupLower.has(lower) || seen.has(lower)) return false;\n seen.add(lower);\n return true;\n });\n },\n {\n message: \"aggregate aliases must be unique and must not collide with `groupBy` column names (case-insensitively — SQL identifiers ignore case)\",\n path: [\"aggregates\"],\n },\n )\n // `orderBy` can only sort what the result actually contains.\n .refine(\n (query) => {\n const sortable = new Set([...(query.groupBy ?? []), ...Object.keys(query.aggregates ?? {})]);\n return (query.orderBy ?? []).every((order) => sortable.has(order.field));\n },\n {\n message: \"every `orderBy.field` must be a `groupBy` column or an aggregate alias\",\n path: [\"orderBy\"],\n },\n );\n\nexport type CollectionQueryAggregate = z.infer<typeof QueryAggregateZ>;\nexport type CollectionQueryWhere = z.infer<typeof QueryWhereZ>;\nexport type CollectionQueryOrder = z.infer<typeof QueryOrderZ>;\nexport type CollectionQuery = z.infer<typeof CollectionQueryZ>;\n","// Compile a validated CollectionQuery (the structured aggregation DSL,\n// `core/queryZ.ts`) into DuckDB SQL over `read_csv`. Safety model:\n// - column names / aliases → double-quoted identifiers (quoteIdent);\n// - every comparison VALUE → a prepared-statement parameter;\n// - the file path is bound by the EXECUTOR as the first parameter;\n// - the DSL itself can't name a table function, so no query can reach\n// any file other than the one the executor binds.\n// Pure + exported for unit tests — no filesystem, no DuckDB here.\n\nimport type { CollectionQuery, CollectionQueryAggregate, CollectionQueryWhere } from \"../core/queryZ\";\nimport { DEFAULT_QUERY_ROWS } from \"../core/queryZ\";\n\n/** One value bound to a `?` placeholder. The DSL only ever compares\n * against JSON scalars, and these are exactly the `DuckDBValue`s the\n * driver accepts — typing the chain end-to-end is what lets `queryCsv`\n * hand them to `runAndReadAll` without coercion. */\nexport type CsvQueryParam = string | number | boolean;\n\n/** Double-quote a SQL identifier (CSV column name / result alias). */\nexport function quoteIdent(name: string): string {\n return `\"${name.replaceAll('\"', '\"\"')}\"`;\n}\n\n/** Single-quote a SQL string literal (a `types={...}` struct key). */\nexport function quoteLiteral(value: string): string {\n return `'${value.replaceAll(\"'\", \"''\")}'`;\n}\n\n/** The `read_csv` argument list shared by every CSV query: the (prepared)\n * path plus a `types` pin forcing the key column to VARCHAR — without it\n * DuckDB's sniffer turns `001` into BIGINT 1, so leading zeros vanish\n * and distinct keys collapse. */\nexport function readCsvArgs(primaryKey: string): string {\n return `?, types={${quoteLiteral(primaryKey)}: 'VARCHAR'}`;\n}\n\n/** One aggregate's SQL expression. `sum`/`avg` TRY_CAST to DOUBLE so a\n * column the sniffer kept as VARCHAR (mixed values) aggregates over its\n * numeric cells instead of erroring; non-numeric cells become NULL and\n * are skipped — standard BI tolerance. `min`/`max` stay native (they are\n * meaningful on strings and dates too). */\nfunction aggregateExpr(aggregate: CollectionQueryAggregate): string {\n const { op, column } = aggregate;\n if (op === \"count\") return column === undefined ? \"count(*)\" : `count(${quoteIdent(column)})`;\n if (op === \"sum\" || op === \"avg\") return `${op}(TRY_CAST(${quoteIdent(column ?? \"\")} AS DOUBLE))`;\n return `${op}(${quoteIdent(column ?? \"\")})`;\n}\n\n/** One where condition → SQL fragment + its bound parameters. String\n * equality compares against `CAST(col AS VARCHAR)` so a sniffer-typed\n * column still matches its textual value; numeric/boolean values compare\n * natively (DuckDB coerces the column side). */\nfunction whereFragment(cond: CollectionQueryWhere): { sql: string; params: CsvQueryParam[] } {\n const column = quoteIdent(cond.field);\n const asText = `CAST(${column} AS VARCHAR)`;\n if (cond.op === \"in\") {\n const values = arrayValue(cond);\n const textual = values.every((value) => typeof value === \"string\");\n const lhs = textual ? asText : column;\n return { sql: `${lhs} IN (${values.map(() => \"?\").join(\", \")})`, params: values };\n }\n // `String()` stays — `contains` matches against text, so a numeric needle is\n // searched by its text form — but an array must fail here like it does on\n // every other scalar op, not silently become the needle \"1,2\".\n if (cond.op === \"contains\") return { sql: `contains(${asText}, ?)`, params: [String(scalarValue(cond))] };\n const operator = { eq: \"=\", ne: \"<>\", gt: \">\", gte: \">=\", lt: \"<\", lte: \"<=\" }[cond.op];\n const lhs = typeof cond.value === \"string\" && (cond.op === \"eq\" || cond.op === \"ne\") ? asText : column;\n return { sql: `${lhs} ${operator} ?`, params: [scalarValue(cond)] };\n}\n\n/** Mirror of `scalarValue` for the one op that takes a set: a scalar under\n * `in` also means the query skipped `CollectionQueryZ`. Left unchecked it\n * failed as `values.every is not a function`, naming neither the field nor\n * the op. */\nfunction arrayValue(cond: CollectionQueryWhere): CsvQueryParam[] {\n if (!Array.isArray(cond.value)) {\n throw new Error(`where condition on '${cond.field}' uses op 'in', which requires an array value, not a scalar`);\n }\n return cond.value;\n}\n\n/** `CollectionQueryZ` refines \"`in` ⇔ array value\", so an array reaching a\n * scalar op means the query was compiled without being validated first —\n * binding it would send an array to a single `?`. */\nfunction scalarValue(cond: CollectionQueryWhere): CsvQueryParam {\n if (Array.isArray(cond.value)) {\n throw new Error(`where condition on '${cond.field}' uses op '${cond.op}', which requires a scalar value, not an array`);\n }\n return cond.value;\n}\n\n/** Compile a validated query against `fromSql` (a table-function call\n * whose FIRST placeholder is the source path — the executor binds it).\n * Returns the SQL and the where-value parameters that follow the path.\n * Callers MUST have run `CollectionQueryZ` first; this function trusts\n * the shape (aliases already charset-checked, orderBy membership already\n * enforced). */\nfunction compileQuery(query: CollectionQuery, fromSql: string): { sql: string; params: CsvQueryParam[] } {\n const groupBy = query.groupBy ?? [];\n const aggregates = Object.entries(query.aggregates ?? {});\n const selectList = [...groupBy.map(quoteIdent), ...aggregates.map(([alias, aggregate]) => `${aggregateExpr(aggregate)} AS ${quoteIdent(alias)}`)];\n const where = (query.where ?? []).map(whereFragment);\n const clauses = [`SELECT ${selectList.join(\", \")}`, `FROM ${fromSql}`];\n if (where.length > 0) clauses.push(`WHERE ${where.map((fragment) => fragment.sql).join(\" AND \")}`);\n if (groupBy.length > 0) clauses.push(`GROUP BY ${groupBy.map(quoteIdent).join(\", \")}`);\n const orderBy = (query.orderBy ?? []).map((order) => quoteIdent(order.field) + (order.dir === \"desc\" ? \" DESC\" : \" ASC\"));\n if (orderBy.length > 0) clauses.push(`ORDER BY ${orderBy.join(\", \")}`);\n clauses.push(`LIMIT ${query.limit ?? DEFAULT_QUERY_ROWS}`);\n return { sql: clauses.join(\" \"), params: where.flatMap((fragment) => fragment.params) };\n}\n\n/** Compile against a CSV file (the dataSource store's engine). */\nexport function compileCsvQuery(query: CollectionQuery, primaryKey: string): { sql: string; params: CsvQueryParam[] } {\n return compileQuery(query, `read_csv(${readCsvArgs(primaryKey)})`);\n}\n\n/** Compile against a JSONL file of ENRICHED records — the file-backed\n * collections' engine (see `jsonlQuery.ts`). No VARCHAR key pin needed:\n * enriched record ids are already strings. `sample_size=-1` makes the\n * schema inference scan EVERY line — with the default sample, a sparse\n * optional/derived field first appearing past the sample would not be\n * inferred as a column and the query would binder-error on it (Codex P2\n * on #2165). The full scan costs nothing extra here: aggregation reads\n * the whole file anyway. */\nexport function compileJsonlQuery(query: CollectionQuery): { sql: string; params: CsvQueryParam[] } {\n return compileQuery(query, `read_json(?, format='newline_delimited', sample_size=-1)`);\n}\n","// DuckDB-backed read-only store over an external CSV file (schema\n// `dataSource`, v1). The user's file is the source of truth and is NEVER\n// modified: non-UTF-8 files (Shift_JIS rosters are the primary use case)\n// are decoded to a content-addressed UTF-8 cache copy under os.tmpdir()\n// and DuckDB reads that copy.\n//\n// Row → record contract:\n// - the schema's `primaryKey` names the CSV column whose value becomes\n// the record id; CSV columns map to fields by NAME;\n// - a key value that isn't a safe record id (Japanese, spaces, …) is\n// hex-encoded (`id0x…`) so detail URLs / remote-view addressing keep\n// working — `displayField` covers presentation;\n// - duplicate key values: LAST row wins (a warn is logged); rows with an\n// empty/missing key are skipped (warn);\n// - `list()` is capped at MAX_CSV_ROWS — the whole existing UI\n// materializes every record, so an uncapped 2M-row file would be a\n// memory bomb. v2 replaces the cap with paging + native aggregation.\n//\n// SQL safety: the row-value comparison is a prepared-statement parameter,\n// never string concatenation; the key column is identifier-quoted. The\n// file path itself is host-resolved (workspace containment in discovery)\n// and also bound as a parameter.\n//\n// DuckDB is a native module; `import()` failures (unsupported platform,\n// broken install) degrade to a thrown, clearly-worded error so ONLY\n// dataSource collections break — see\n// packages/core/assets/helps/error-recovery.md.\n\nimport { fieldTextOrNull } from \"../core/fieldText\";\nimport { BackendUnavailableError } from \"./backendAvailability\";\nimport { lstat, mkdir, open, readdir, readFile, rename, stat, unlink, writeFile } from \"node:fs/promises\";\nimport { createHash, randomBytes } from \"node:crypto\";\nimport { tmpdir } from \"node:os\";\nimport path from \"node:path\";\nimport iconv from \"iconv-lite\";\nimport type { CollectionItem } from \"../core/schema\";\nimport type { CollectionQuery } from \"../core/queryZ\";\nimport type { DuckDBInstance } from \"@duckdb/node-api\";\nimport { compileCsvQuery, quoteIdent, readCsvArgs, type CsvQueryParam } from \"./csvQuery\";\nimport { getWorkspaceRoot, log } from \"./host\";\nimport { isContainedInRoot, safeRecordId } from \"./paths\";\nimport { isErrorWithCode } from \"@mulmoclaude/common\";\n\n/** `list()` row cap. Over-cap files are truncated with a warn — the v1\n * contract is \"browse + per-record views\", not full-table analytics. */\nexport const MAX_CSV_ROWS = 5000;\n\n/** Record ids minted from non-safe key values: `id0x` + utf-8 hex. Raw key\n * values that themselves match this pattern are ALSO encoded, so the\n * encoded namespace never collides with a raw value (injective mapping). */\nconst ENCODED_ID_PATTERN = /^id0x([0-9a-f]+)$/;\n\n/** A CSV key value → the record id it's addressed by. Safe values pass\n * through untouched; everything else (and anything shaped like an encoded\n * id) becomes `id0x<hex>`. Pure + exported for unit tests. */\nexport function encodeCsvRecordId(rawKey: string): string {\n if (safeRecordId(rawKey) === rawKey && !ENCODED_ID_PATTERN.test(rawKey)) return rawKey;\n return `id0x${Buffer.from(rawKey, \"utf-8\").toString(\"hex\")}`;\n}\n\n/** A record id → the CSV key value to look up. Inverse of\n * `encodeCsvRecordId` for encoded ids; anything else is already the raw\n * value. Pure + exported for unit tests. */\nexport function decodeCsvRecordId(itemId: string): string {\n const hex = ENCODED_ID_PATTERN.exec(itemId)?.[1];\n if (hex === undefined) return itemId;\n return Buffer.from(hex, \"hex\").toString(\"utf-8\");\n}\n\n/** Normalize one DuckDB JS value into a JSON-safe record value: BigInt →\n * number (string beyond the safe range), DATE/TIMESTAMP → ISO string\n * (date-only when the clock is exactly UTC midnight, matching the `date`\n * field contract), exotic DuckDB values → their string form. Pure +\n * exported for unit tests. */\n/** `JSON.stringify` restricted to what a CSV cell can survive. Returns the\n * serialised value, or `String(value)` when serialisation is impossible —\n * losing the content of one cell is bad, failing the entire query is worse. */\nfunction safeJsonCell(value: object): string {\n try {\n const json = JSON.stringify(value, (_key, entry: unknown) => (typeof entry === \"bigint\" ? entry.toString() : entry));\n // eslint-disable-next-line @typescript-eslint/no-base-to-string -- last resort: JSON.stringify returned undefined (an object whose toJSON does). \"[object Object]\" at least says \"this cell held an object\"; \"\" would erase that it existed.\n return json ?? String(value);\n } catch {\n // eslint-disable-next-line @typescript-eslint/no-base-to-string -- the serialiser threw (circular ref). Same reasoning as above: a placeholder beats losing the cell entirely.\n return String(value);\n }\n}\n\nexport function normalizeCsvValue(value: unknown): unknown {\n if (typeof value === \"bigint\") {\n return value <= BigInt(Number.MAX_SAFE_INTEGER) && value >= BigInt(-Number.MAX_SAFE_INTEGER) ? Number(value) : value.toString();\n }\n if (value instanceof Date) {\n const iso = value.toISOString();\n return iso.endsWith(\"T00:00:00.000Z\") ? iso.slice(0, 10) : iso;\n }\n // A DuckDB STRUCT / LIST / MAP arrives as an object. `String(...)` renders it\n // \"[object Object]\" — the cell's content is simply gone. Serialise instead:\n // this is a data cell, not an id or a matcher key, so preserving what the\n // column holds beats reducing it to a placeholder.\n //\n // Guarded, because `JSON.stringify` is not total on what DuckDB can hand us:\n // it throws on a BIGINT nested in a struct (the branch above only unwraps a\n // top-level bigint) and on a circular ref, and returns undefined when an\n // object's `toJSON` does. A cell that cannot be serialised falls back to the\n // old rendering rather than aborting the whole read.\n if (value !== null && typeof value === \"object\") return safeJsonCell(value);\n return value;\n}\n\n/** One raw DuckDB row → a CollectionItem, or null when the key cell is\n * missing/empty (the row can't be addressed). The primaryKey field is\n * OVERWRITTEN with the (possibly encoded) record id so `item[primaryKey]`\n * and the record's address never drift — same invariant the file store's\n * write path enforces. Pure + exported for unit tests. */\nexport function csvRowToItem(row: Record<string, unknown>, primaryKey: string): CollectionItem | null {\n const normalized = Object.fromEntries(Object.entries(row).map(([key, value]) => [key, normalizeCsvValue(value)]));\n const rawKey = normalized[primaryKey];\n // Identity: same rule as every other primaryKey read. `normalizeCsvValue` has\n // already flattened objects to JSON, so anything without a text form here is\n // genuinely unaddressable.\n const keyText = fieldTextOrNull(rawKey);\n if (keyText === null || keyText === \"\") return null;\n return { ...normalized, [primaryKey]: encodeCsvRecordId(keyText) };\n}\n\n/** Dedupe by record id, LAST row wins (matches `csvRead`'s last-match\n * pick). Returns the surviving items in first-seen order. Pure +\n * exported for unit tests. */\nexport function dedupeByRecordId(items: CollectionItem[], primaryKey: string): { items: CollectionItem[]; duplicates: number } {\n const byId = new Map<string, CollectionItem>();\n for (const item of items) byId.set(String(item[primaryKey]), item);\n return { items: [...byId.values()], duplicates: items.length - byId.size };\n}\n\n/** True when a thrown DuckDB error is the `types` pin naming a column the\n * CSV doesn't have — the schema/file-mismatch case the caller downgrades\n * to \"empty collection + warn\" instead of a 500. */\nfunction isMissingKeyColumnError(err: unknown): boolean {\n return String(err).includes(\"do not exist in the CSV\");\n}\n\n// ---------------------------------------------------------------------------\n// Encoding: never touch the user's file — decode to a tmpdir cache copy\n// ---------------------------------------------------------------------------\n\n/** Bytes sniffed for UTF-8 validity. The trailing 3 bytes of the sample\n * are dropped so a multibyte char split at the boundary can't produce a\n * false negative on a valid file. */\nconst SNIFF_BYTES = 1024 * 1024;\n\nfunction isValidUtf8(buf: Buffer): boolean {\n try {\n new TextDecoder(\"utf-8\", { fatal: true }).decode(buf);\n return true;\n } catch {\n return false;\n }\n}\n\n/** Detect the (best-effort) encoding of a non-UTF-8 buffer. BOMs decide\n * UTF-16; otherwise cp932 (the Shift_JIS superset — Excel-exported\n * Japanese CSVs are the primary non-UTF-8 case this feature serves). */\nfunction fallbackEncoding(buf: Buffer): string {\n if (buf.length >= 2 && buf[0] === 0xff && buf[1] === 0xfe) return \"utf-16le\";\n if (buf.length >= 2 && buf[0] === 0xfe && buf[1] === 0xff) return \"utf-16be\";\n return \"cp932\";\n}\n\nexport function cacheDir(): string {\n return path.join(tmpdir(), \"mulmoclaude-csv-utf8\");\n}\n\n/** Read only the first `bytes` of a file — the encoding sniff must not\n * pull a multi-hundred-MB CSV into memory on the (common) UTF-8 path. */\nasync function readHead(absPath: string, bytes: number): Promise<Buffer> {\n const handle = await open(absPath, \"r\");\n try {\n const { size } = await handle.stat();\n const buf = Buffer.alloc(Math.min(bytes, size));\n await handle.read(buf, 0, buf.length, 0);\n return buf;\n } finally {\n await handle.close();\n }\n}\n\n/** Decode the whole file into a UTF-8 cache copy and return its path.\n * Cache key = (path, mtime, size), so a replaced CSV re-decodes and an\n * unchanged one never does. */\nasync function pathExists(target: string): Promise<boolean> {\n try {\n await stat(target);\n return true;\n } catch {\n return false;\n }\n}\n\n/** Best-effort removal of older decode-cache entries for the same source\n * path — a frequently-replaced large CSV would otherwise accumulate one\n * full copy per (mtime, size) forever. Runs AFTER the current copy is\n * published; a concurrent reader holding an old fd is unaffected\n * (unlink-while-open is safe on POSIX). */\nasync function evictSupersededCache(key: string, keepBasename: string): Promise<void> {\n try {\n const entries = await readdir(cacheDir());\n await Promise.all(\n entries.filter((name) => name.startsWith(`${key}-`) && name !== keepBasename).map((name) => unlink(path.join(cacheDir(), name)).catch(() => undefined)),\n );\n } catch {\n // cache dir missing / unreadable — nothing to evict\n }\n}\n\n/** Decode the whole file into a UTF-8 cache copy and return its path.\n * Cache key = (path, mtime, size), so a replaced CSV re-decodes and an\n * unchanged one never does; superseded copies are evicted. The cache\n * lives in the SHARED OS tmpdir, so the dir is 0700 and files 0600 —\n * decoded rows must not be readable by other local users. */\nasync function decodeToCache(absPath: string, info: { mtimeMs: number; size: number }): Promise<string> {\n const key = createHash(\"sha256\").update(absPath).digest(\"hex\").slice(0, 16);\n const cached = path.join(cacheDir(), `${key}-${Math.trunc(info.mtimeMs)}-${info.size}.csv`);\n if (!(await pathExists(cached))) {\n const whole = await readFile(absPath);\n const encoding = fallbackEncoding(whole);\n const text = iconv.decode(whole, encoding);\n await mkdir(cacheDir(), { recursive: true, mode: 0o700 });\n // Unique tmp name + rename in the SAME dir — atomic publish, and a\n // concurrent decode of the same file just wins/loses the rename cleanly.\n const tmp = `${cached}.${randomBytes(4).toString(\"hex\")}.tmp`;\n await writeFile(tmp, text, { encoding: \"utf-8\", mode: 0o600 });\n await rename(tmp, cached);\n log.info(\"collections\", \"decoded non-UTF-8 dataSource file to cache\", { path: absPath, encoding });\n await evictSupersededCache(key, path.basename(cached));\n }\n return cached;\n}\n\n/** Re-validate the dataSource file at READ time, mirroring the JSON\n * store's per-read defenses: realpath containment (a symlink swapped in\n * after discovery must not walk out of the workspace) and an lstat\n * regular-file check (a symlink leaf is refused outright, even one\n * pointing inside the workspace — same rule as `isRegularFile` on\n * record files). Returns the stat info, or null for \"no readable file\"\n * (ENOENT / refused), which callers render as an empty collection. */\nasync function safeCsvStat(absPath: string, workspaceRoot: string): Promise<{ mtimeMs: number; size: number } | null> {\n if (!isContainedInRoot(absPath, workspaceRoot)) {\n log.warn(\"collections\", \"dataSource read refused: path escapes workspace\", { path: absPath });\n return null;\n }\n let info;\n try {\n info = await lstat(absPath);\n } catch (err) {\n if (isErrorWithCode(err) && err.code === \"ENOENT\") return null;\n throw err;\n }\n if (!info.isFile()) {\n log.warn(\"collections\", \"dataSource read refused: not a regular file (symlink?)\", { path: absPath });\n return null;\n }\n return info;\n}\n\n/** Return a path DuckDB can read as UTF-8: the original file when it\n * already is UTF-8 (the cheap, common case — only the head is sniffed),\n * else a decoded cache copy (see `decodeToCache`). Returns null when\n * there is no readable file (missing, symlink, or containment-refused —\n * see `safeCsvStat`), which callers render as an empty collection. */\nasync function ensureUtf8CsvPath(absPath: string, workspaceRoot: string): Promise<string | null> {\n const info = await safeCsvStat(absPath, workspaceRoot);\n if (info === null) return null;\n const head = await readHead(absPath, SNIFF_BYTES);\n // Drop the tail bytes of a full-length sample so a multibyte char split\n // at the boundary can't read as invalid UTF-8.\n const sample = head.length === SNIFF_BYTES ? head.subarray(0, SNIFF_BYTES - 3) : head;\n const hasUtf16Bom = head.length >= 2 && ((head[0] === 0xff && head[1] === 0xfe) || (head[0] === 0xfe && head[1] === 0xff));\n if (!hasUtf16Bom && isValidUtf8(sample)) return absPath;\n return decodeToCache(absPath, info);\n}\n\n// ---------------------------------------------------------------------------\n// DuckDB plumbing\n// ---------------------------------------------------------------------------\n\nlet instancePromise: Promise<DuckDBInstance> | null = null;\n\n/** Lazily create one shared in-memory DuckDB instance. The dynamic import\n * keeps the native module OUT of core's load path — a platform where the\n * prebuilt binding is missing degrades to a per-query error on dataSource\n * collections only, never a broken core. A failed init is retried on the\n * next call (the promise is reset). */\nasync function duckDbInstance(): Promise<DuckDBInstance> {\n if (instancePromise === null) {\n instancePromise = import(\"@duckdb/node-api\").then((mod) => mod.DuckDBInstance.create(\":memory:\"));\n }\n try {\n return await instancePromise;\n } catch (err) {\n instancePromise = null;\n throw new BackendUnavailableError(\n `DuckDB is unavailable on this host (@duckdb/node-api failed to load: ${String(err)}) — dataSource collections cannot be read`,\n );\n }\n}\n\nexport async function queryCsv(sql: string, params: CsvQueryParam[]): Promise<Record<string, unknown>[]> {\n const instance = await duckDbInstance();\n const connection = await instance.connect();\n try {\n const reader = await connection.runAndReadAll(sql, params);\n return reader.getRowObjectsJS();\n } finally {\n connection.disconnectSync();\n }\n}\n\n// ---------------------------------------------------------------------------\n// The store operations (consumed by storeFor in ./store)\n// ---------------------------------------------------------------------------\n\n/** Every row of the CSV as records — capped, deduped, id-encoded. The\n * key column is pinned to VARCHAR (see `readCsvArgs`). `workspaceRoot`\n * drives the per-read containment check; omitted, the configured host\n * root is used. */\nexport interface CsvListResult {\n items: CollectionItem[];\n /** True when the scan stopped at `MAX_CSV_ROWS` — `items` is then a\n * row-capped prefix, not the whole file. Surfaced to `ListPage.truncated`\n * so callers see the cap instead of inferring it from a warn log. */\n truncated: boolean;\n}\n\nexport async function csvList(absPath: string, primaryKey: string, workspaceRoot?: string): Promise<CsvListResult> {\n const utf8Path = await ensureUtf8CsvPath(absPath, workspaceRoot ?? getWorkspaceRoot());\n if (utf8Path === null) return { items: [], truncated: false };\n let rows: Record<string, unknown>[];\n try {\n rows = await queryCsv(`SELECT * FROM read_csv(${readCsvArgs(primaryKey)}) LIMIT ${MAX_CSV_ROWS + 1}`, [utf8Path]);\n } catch (err) {\n // The VARCHAR pin names a column the CSV doesn't have — a schema/file\n // mismatch, rendered as an empty collection with a warn (same outcome\n // the pre-pin `primaryKey in row` check produced), not a 500.\n if (!isMissingKeyColumnError(err)) throw err;\n log.warn(\"collections\", \"dataSource CSV has no primaryKey column — every row is skipped\", { path: absPath, primaryKey });\n return { items: [], truncated: false };\n }\n const truncated = rows.length > MAX_CSV_ROWS;\n if (truncated) {\n log.warn(\"collections\", \"dataSource CSV truncated to row cap\", { path: absPath, cap: MAX_CSV_ROWS });\n rows.length = MAX_CSV_ROWS;\n }\n const items = rows.map((row) => csvRowToItem(row, primaryKey)).filter((item): item is CollectionItem => item !== null);\n const skipped = rows.length - items.length;\n if (skipped > 0) log.warn(\"collections\", \"dataSource CSV rows skipped (empty key cell)\", { path: absPath, skipped });\n const deduped = dedupeByRecordId(items, primaryKey);\n if (deduped.duplicates > 0)\n log.warn(\"collections\", \"dataSource CSV has duplicate key values (last row wins)\", { path: absPath, duplicates: deduped.duplicates });\n return { items: deduped.items, truncated };\n}\n\n/** The scan-order ordinal column the last-match read adds. Underscore\n * prefix keeps it out of any plausible CSV header namespace; it is\n * stripped from the returned record either way. */\nconst ROW_ORDINAL = \"__mc_row\";\n\n/** One record by id. The comparison value rides as a prepared-statement\n * parameter, and the LAST matching row is selected IN DuckDB (scan-order\n * ordinal + LIMIT 1) — a CSV with thousands of duplicate keys must not\n * materialize them all for one detail read. Consistent with csvList's\n * last-wins dedupe. */\nexport async function csvRead(absPath: string, primaryKey: string, itemId: string, workspaceRoot?: string): Promise<CollectionItem | null> {\n const utf8Path = await ensureUtf8CsvPath(absPath, workspaceRoot ?? getWorkspaceRoot());\n if (utf8Path === null) return null;\n const rawKey = decodeCsvRecordId(itemId);\n // Errors (missing key column, malformed CSV, DuckDB unavailable)\n // propagate — a clear 500 with the DuckDB message beats a silent 404.\n const sql =\n `SELECT * FROM (SELECT *, row_number() OVER () AS ${quoteIdent(ROW_ORDINAL)} FROM read_csv(${readCsvArgs(primaryKey)})) ` +\n `WHERE CAST(${quoteIdent(primaryKey)} AS VARCHAR) = ? ORDER BY ${quoteIdent(ROW_ORDINAL)} DESC LIMIT 1`;\n const rows = await queryCsv(sql, [utf8Path, rawKey]);\n const last = rows.at(0);\n if (last === undefined) return null;\n const { [ROW_ORDINAL]: __ordinal, ...record } = last;\n return csvRowToItem(record, primaryKey);\n}\n\n/** Run a validated aggregation query (the structured DSL — see\n * `core/queryZ.ts`) over the WHOLE file: no row cap on the scan (a\n * capped aggregate would be a wrong number), only the result-row LIMIT\n * the compiler emits. Values are normalized like list/read rows so a\n * chart consumer gets plain JSON scalars. */\nexport async function csvRunQuery(absPath: string, primaryKey: string, query: CollectionQuery, workspaceRoot?: string): Promise<Record<string, unknown>[]> {\n const utf8Path = await ensureUtf8CsvPath(absPath, workspaceRoot ?? getWorkspaceRoot());\n if (utf8Path === null) return [];\n const { sql, params } = compileCsvQuery(query, primaryKey);\n const rows = await queryCsv(sql, [utf8Path, ...params]);\n return rows.map((row) => Object.fromEntries(Object.entries(row).map(([key, value]) => [key, normalizeCsvValue(value)])));\n}\n","// Shared `fs.watch` plumbing for the filesystem-backed stores.\n//\n// Every filesystem backend hits the same three quirks, so they are solved\n// once here rather than in each store:\n//\n// - `fs.watch` throws on a missing directory, so the watch has to be armed\n// lazily and re-armed rather than assumed;\n// - a filename arrives as `null` on some platforms (we then can't say which\n// record changed, only that something did);\n// - `filename` is typed `string` but can arrive as a Buffer, which has no\n// `startsWith` — calling it directly throws inside the callback and takes\n// the watcher down with it.\n//\n// Single-artifact backends (one CSV, one db file) additionally watch the\n// PARENT directory rather than the file: an atomic replace swaps the inode,\n// and a watch bound to the old one goes silently deaf.\n\nimport { realpathSync, watch, type FSWatcher } from \"node:fs\";\nimport { mkdir } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport { log } from \"./host\";\n\n/** An atomic file replace (editor save, `mv` over the target) surfaces as\n * 2-3 events. Collapse them so one user action reports one change. */\nconst REPLACE_DEBOUNCE_MS = 300;\n\n/** The path to hand `watch()`, with Windows 8.3 short names resolved away.\n *\n * ReadDirectoryChangesW reports filenames against the LONG path, but a watch\n * opened on a short path (`C:\\Users\\RUNNER~1\\…` — what `os.tmpdir()` returns\n * on GitHub's Windows runners) keeps the short form. libuv's\n * `assert(!_wcsnicmp(filename, dir, dirlen))` in `src/win/fs-event.c` then\n * aborts the PROCESS on the first event — a native assert, so neither\n * `watcher.on(\"error\")` nor a try/catch can contain it.\n *\n * POSIX is deliberately left alone: `realpath` there also collapses symlinks\n * (`/var` → `/private/var` on macOS), which we neither need nor want to\n * change. A failure falls back to the original path — worst case we are no\n * worse off than before. */\nfunction watchablePath(dir: string): string {\n if (process.platform !== \"win32\") return dir;\n try {\n return realpathSync.native(dir);\n } catch {\n return dir;\n }\n}\n\nexport interface FsWatchHandle {\n close: () => void;\n}\n\n/** Watch `dir`, reporting each accepted filename. `accept` decides what is\n * noise; a null filename always passes (the platform didn't tell us which\n * file, so the caller must assume the worst). */\nexport async function watchDirectory(\n dir: string,\n accept: (filename: string) => boolean,\n onHit: (filename: string | null) => void,\n): Promise<FsWatchHandle | null> {\n try {\n await mkdir(dir, { recursive: true });\n const watcher: FSWatcher = watch(watchablePath(dir), { persistent: false }, (_eventType, rawFilename) => {\n // Defensive stringify: the callback's `string` typing is a lie on some\n // platforms, and a Buffer reaching `accept` would throw in here — which\n // kills the watcher, not just the event.\n const filename = rawFilename === null ? null : String(rawFilename);\n if (filename !== null && !accept(filename)) return;\n onHit(filename);\n });\n watcher.on(\"error\", (err) => {\n log.warn(\"collections\", \"fs watch error\", { dir, error: String(err) });\n });\n return { close: () => watcher.close() };\n } catch (err) {\n log.warn(\"collections\", \"fs watch start failed\", { dir, error: String(err) });\n return null;\n }\n}\n\n/** Watch the single file `absPath` by watching its PARENT directory, so an\n * atomic replace can't strand the watch on a dead inode. `alsoAccept`\n * widens the filter beyond the exact basename (sqlite's `-wal`/`-journal`\n * sidecars). Reports are debounced: one replace, one call. */\nexport async function watchSingleFile(\n absPath: string,\n alsoAccept: (basename: string, filename: string) => boolean,\n onChange: () => void,\n): Promise<FsWatchHandle | null> {\n const dir = path.dirname(absPath);\n const base = path.basename(absPath);\n let timer: ReturnType<typeof setTimeout> | null = null;\n const fire = (): void => {\n if (timer) clearTimeout(timer);\n timer = setTimeout(() => {\n timer = null;\n onChange();\n }, REPLACE_DEBOUNCE_MS);\n timer.unref?.();\n };\n const handle = await watchDirectory(dir, (filename) => filename === base || alsoAccept(base, filename), fire);\n if (!handle) return null;\n return {\n close: () => {\n if (timer) clearTimeout(timer);\n timer = null;\n handle.close();\n },\n };\n}\n\n/** An `FsWatchHandle` as a bare unsubscribe — `null` straight through, so an\n * unarmed watch stays distinguishable from an armed one. Lives here rather\n * than beside the store contract so both `store.ts` and the backends it\n * registers can reach it without importing each other. */\nexport function closerFor(handle: FsWatchHandle | null): (() => void) | null {\n return handle === null ? null : () => handle.close();\n}\n","// SQLite-backed WRITABLE store (schema `storage: { type: \"sqlite\" }`) —\n// the first non-file backend, added to validate the CollectionStore\n// abstraction (plans/done/refactor-storage-virtualization.md, Stage 4).\n//\n// Layout: one database file (`schema.storage.path`, workspace-contained at\n// discovery AND re-checked here), one table\n// `records(id TEXT PRIMARY KEY, record TEXT NOT NULL)` holding each\n// record's JSON. The schema's primaryKey value is the row id, gated by the\n// SAME `safeRecordId` rule as the file store's `<id>.json` names, so ids\n// stay portable across backends and remote-view preflights keep holding.\n//\n// Engine: `node:sqlite` — no native npm dependency (DuckDB pain avoided).\n// The app's engines floor is Node >= 20.12 but node:sqlite needs >= 22.5,\n// so the module is imported LAZILY and a failure degrades to a thrown,\n// clearly-worded error — ONLY sqlite-backed collections break, exactly the\n// DuckDB pattern in csvStore.ts. See\n// packages/core/assets/helps/error-recovery.md.\n//\n// Order contract: list/page walk `ORDER BY id` (BINARY collation =\n// codepoint order) — the same documented lexicographic-by-record-id order\n// as the file store. Paging is NATIVE (LIMIT/OFFSET + COUNT(*)), the\n// store's `nativePaging: true` flag is honest.\n//\n// Change events: io.ts is not involved here, so this store publishes its\n// own `publishCollectionChange` after each successful write/delete —\n// keeping the CollectionStore contract \"a successful write/delete\n// publishes\" true for every backend.\n\nimport { lstat, mkdir } from \"node:fs/promises\";\nimport { closerFor, watchSingleFile } from \"./watchFs\";\nimport { BackendUnavailableError } from \"./backendAvailability\";\nimport path from \"node:path\";\nimport { hasNumberProp, isErrorWithCode, isRecord } from \"@mulmoclaude/common\";\nimport type { CollectionItem } from \"../core/schema\";\nimport type { LoadedCollection } from \"./discoveredCollection\";\nimport type { DeleteItemResult, IoOptions, WriteItemResult } from \"./io\";\nimport { collectionChangePayload, getWorkspaceRoot, log, publishCollectionChange } from \"./host\";\nimport { isContainedInRoot, safeRecordId } from \"./paths\";\nimport { projectItemFields, type ListOptions, type ListPage, type WriteOptions } from \"./storePage\";\nimport type { CollectionStore } from \"./store\";\n\n// Minimal structural view of node:sqlite — typed locally so the build does\n// not depend on @types/node shipping the (still experimental) module types.\ninterface SqliteStatement {\n all: (...params: (string | number)[]) => unknown[];\n get: (...params: (string | number)[]) => unknown;\n run: (...params: (string | number)[]) => { changes: number | bigint };\n}\ninterface SqliteDatabase {\n prepare: (sql: string) => SqliteStatement;\n exec: (sql: string) => void;\n close: () => void;\n}\ninterface SqliteModule {\n DatabaseSync: new (dbPath: string) => SqliteDatabase;\n}\n\n/** A constructor's parameter and return types are not observable at runtime,\n * so the check stops at \"DatabaseSync is constructible\" — the only member of\n * the module this store ever touches. */\nfunction isSqliteModule(mod: unknown): mod is SqliteModule {\n return isRecord(mod) && typeof mod.DatabaseSync === \"function\";\n}\n\nlet sqliteModule: Promise<SqliteModule> | null = null;\n\n/** Drops the memo first so a later call can retry (e.g. tests stubbing the\n * runtime), then reports why the backend is unusable. */\nfunction sqliteUnavailable(reason: string): never {\n sqliteModule = null;\n throw new BackendUnavailableError(`sqlite storage needs the node:sqlite module (Node.js >= 22.5) — this runtime cannot load it: ${reason}`);\n}\n\n/** Lazy-load node:sqlite once. A runtime without it (Node < 22.5) throws a\n * clearly-worded error the caller surfaces — never a bare MODULE_NOT_FOUND. */\nfunction loadSqlite(): Promise<SqliteModule> {\n sqliteModule ??= import(\"node:sqlite\").then(\n (mod) => (isSqliteModule(mod) ? mod : sqliteUnavailable(\"the module exposes no DatabaseSync constructor\")),\n (err: unknown) => sqliteUnavailable(String(err)),\n );\n return sqliteModule;\n}\n\n/** The db file's on-disk state. A symlink or non-regular file is refused\n * (file-disclosure defense, same rule as io.ts record files); ENOENT is\n * just \"no records yet\". Any OTHER lstat failure (EACCES, EIO, …) is\n * rethrown so reads surface a real filesystem problem instead of\n * silently reporting an empty collection. */\nasync function dbFileState(absPath: string): Promise<\"missing\" | \"file\" | \"refused\"> {\n try {\n const info = await lstat(absPath);\n return info.isFile() ? \"file\" : \"refused\";\n } catch (err) {\n if (isErrorWithCode(err) && err.code === \"ENOENT\") return \"missing\";\n throw err;\n }\n}\n\nconst CREATE_TABLE = \"CREATE TABLE IF NOT EXISTS records (id TEXT PRIMARY KEY, record TEXT NOT NULL)\";\n\ntype DbHandle = { kind: \"ok\"; database: SqliteDatabase } | { kind: \"missing\" } | { kind: \"refused\" };\n\n/** Open the database for one operation, classifying the two unavailable\n * states so callers can map them honestly (`refused` ⇒ path-escape,\n * `missing` ⇒ empty / not-found — conflating them would misreport a\n * containment escape as \"item not found\"). The containment pre-check runs\n * BEFORE mkdir even when the file is missing — `isContainedInRoot`\n * resolves through the closest existing ancestor, so a symlinked-away\n * parent can never make the recursive mkdir create directories outside\n * the workspace (same pre/post belt-and-suspenders as io.ts writes). */\nasync function openDb(absPath: string, workspaceRoot: string, mode: \"read\" | \"write\"): Promise<DbHandle> {\n const state = await dbFileState(absPath);\n if (state === \"refused\") {\n log.warn(\"collections\", \"sqlite database refused: not a regular file\", { path: absPath });\n return { kind: \"refused\" };\n }\n if (!isContainedInRoot(path.dirname(absPath), workspaceRoot)) {\n log.warn(\"collections\", \"sqlite refused: database dir escapes workspace via symlink\", { path: absPath });\n return { kind: \"refused\" };\n }\n if (mode === \"read\" && state === \"missing\") return { kind: \"missing\" };\n if (mode === \"write\") {\n await mkdir(path.dirname(absPath), { recursive: true });\n if (!isContainedInRoot(path.dirname(absPath), workspaceRoot)) {\n log.warn(\"collections\", \"sqlite write refused: database dir escapes workspace via symlink (post-mkdir)\", { path: absPath });\n return { kind: \"refused\" };\n }\n }\n const { DatabaseSync } = await loadSqlite();\n const database = new DatabaseSync(absPath);\n // Wait for a concurrent writer's lock instead of failing fast with\n // SQLITE_BUSY. PRAGMA (not the constructor's `timeout` option, which\n // only exists on Node >= 22.16 — our sqlite floor is 22.5).\n database.exec(\"PRAGMA busy_timeout = 5000\");\n database.exec(CREATE_TABLE);\n return { kind: \"ok\", database };\n}\n\n/** Run `operation` against the database and always close it; unavailable\n * states resolve through `onUnavailable` so each caller maps `missing`\n * vs `refused` to its own result kind. */\nasync function withDb<T>(\n absPath: string,\n workspaceRoot: string,\n mode: \"read\" | \"write\",\n onUnavailable: (reason: \"missing\" | \"refused\") => T,\n operation: (database: SqliteDatabase) => T | Promise<T>,\n): Promise<T> {\n const handle = await openDb(absPath, workspaceRoot, mode);\n if (handle.kind !== \"ok\") return onUnavailable(handle.kind);\n try {\n return await operation(handle.database);\n } finally {\n handle.database.close();\n }\n}\n\n// SQLite extended result codes for the duplicate-id failure our INSERT hits:\n// a PRIMARY KEY clash, and the plain-UNIQUE-index equivalent.\nconst SQLITE_CONSTRAINT_PRIMARYKEY = 1555;\nconst SQLITE_CONSTRAINT_UNIQUE = 2067;\n\n/** node:sqlite throws ERR_SQLITE_ERROR with the SQLite extended result\n * code on `errcode`. Checked structurally (message text kept only as a\n * fallback for runtimes that don't expose `errcode`). */\nfunction isUniqueConstraintError(err: unknown): boolean {\n if (hasNumberProp(err, \"errcode\")) return err.errcode === SQLITE_CONSTRAINT_PRIMARYKEY || err.errcode === SQLITE_CONSTRAINT_UNIQUE;\n return String(err).includes(\"UNIQUE constraint\");\n}\n\nfunction parseRow(raw: unknown): CollectionItem | null {\n if (typeof raw !== \"string\") return null;\n try {\n const parsed: unknown = JSON.parse(raw);\n return isRecord(parsed) ? parsed : null;\n } catch {\n return null;\n }\n}\n\n/** One column of a result row. node:sqlite types rows as `unknown`, so a\n * value that is not a row object yields no column at all. */\nfunction readColumn(row: unknown, column: string): unknown {\n return isRecord(row) ? row[column] : undefined;\n}\n\nfunction rowsToItems(rows: unknown[]): CollectionItem[] {\n return rows.map((row) => parseRow(readColumn(row, \"record\"))).filter((item): item is CollectionItem => item !== null);\n}\n\n/** node:sqlite hands back an integer column as `number`, or as `bigint` once\n * it leaves the safe-integer range — COUNT(*) can be either. */\nfunction countRecords(database: SqliteDatabase): number {\n const count = readColumn(database.prepare(\"SELECT COUNT(*) AS n FROM records\").get(), \"n\");\n if (typeof count === \"number\") return count;\n if (typeof count === \"bigint\") return Number(count);\n throw new Error(`sqlite COUNT(*) returned no numeric row count (got ${typeof count})`);\n}\n\nasync function sqliteList(absPath: string, workspaceRoot: string): Promise<CollectionItem[]> {\n return withDb<CollectionItem[]>(\n absPath,\n workspaceRoot,\n \"read\",\n () => [],\n (database) => rowsToItems(database.prepare(\"SELECT record FROM records ORDER BY id\").all()),\n );\n}\n\nasync function sqlitePage(absPath: string, primaryKey: string, opts: ListOptions, workspaceRoot: string): Promise<ListPage> {\n const emptyPage: ListPage = { items: [], total: 0, truncated: false };\n return withDb(\n absPath,\n workspaceRoot,\n \"read\",\n () => emptyPage,\n (database) => {\n const total = countRecords(database);\n const offset = Math.max(0, opts.offset ?? 0);\n const limit = opts.limit === undefined ? -1 : Math.max(0, opts.limit); // LIMIT -1 = unbounded in SQLite\n const rows = database.prepare(\"SELECT record FROM records ORDER BY id LIMIT ? OFFSET ?\").all(limit, offset);\n return { items: projectItemFields(rowsToItems(rows), opts.fields, primaryKey), total, truncated: false };\n },\n );\n}\n\nasync function sqliteRead(absPath: string, itemId: string, workspaceRoot: string): Promise<CollectionItem | null> {\n const safeId = safeRecordId(itemId);\n if (safeId === null) return null;\n return withDb<CollectionItem | null>(\n absPath,\n workspaceRoot,\n \"read\",\n () => null,\n (database) => {\n const row = database.prepare(\"SELECT record FROM records WHERE id = ?\").get(safeId);\n return parseRow(readColumn(row, \"record\"));\n },\n );\n}\n\nasync function sqliteWrite(\n absPath: string,\n itemId: string,\n item: CollectionItem,\n opts: { workspaceRoot: string; publishRoot?: string | undefined; slug?: string | undefined; refuseOverwrite?: boolean | undefined },\n): Promise<WriteItemResult> {\n const safeId = safeRecordId(itemId);\n if (safeId === null) return { kind: \"invalid-id\", itemId };\n const outcome = await withDb<WriteItemResult>(\n absPath,\n opts.workspaceRoot,\n \"write\",\n () => ({ kind: \"path-escape\", itemId: safeId }),\n (database) => {\n const payload = JSON.stringify(item);\n if (opts.refuseOverwrite) {\n // The PRIMARY KEY constraint is the race-safe create gate — the\n // sqlite twin of the file store's O_EXCL open.\n try {\n database.prepare(\"INSERT INTO records (id, record) VALUES (?, ?)\").run(safeId, payload);\n } catch (err) {\n if (isUniqueConstraintError(err)) return { kind: \"conflict\", itemId: safeId };\n throw err;\n }\n } else {\n database.prepare(\"INSERT INTO records (id, record) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET record = excluded.record\").run(safeId, payload);\n }\n return { kind: \"ok\", itemId: safeId, item };\n },\n );\n // Publish AFTER the write lands (same ordering rule as io.ts) so a live\n // subscriber that refetches always sees the new record.\n if (outcome.kind === \"ok\" && opts.slug) publishCollectionChange(collectionChangePayload({ slug: opts.slug, ids: [safeId], op: \"upsert\" }, opts.publishRoot));\n return outcome;\n}\n\nasync function sqliteDelete(\n absPath: string,\n itemId: string,\n opts: { workspaceRoot: string; publishRoot?: string | undefined; slug?: string },\n): Promise<DeleteItemResult> {\n const safeId = safeRecordId(itemId);\n if (safeId === null) return { kind: \"invalid-id\", itemId };\n // `missing` db = nothing was ever written ⇒ not-found; `refused`\n // (containment/symlink) must surface as path-escape, never as a 404.\n const outcome = await withDb<DeleteItemResult>(\n absPath,\n opts.workspaceRoot,\n \"read\",\n (reason) => (reason === \"refused\" ? { kind: \"path-escape\", itemId: safeId } : { kind: \"not-found\", itemId: safeId }),\n (database) => {\n const { changes } = database.prepare(\"DELETE FROM records WHERE id = ?\").run(safeId);\n return Number(changes) === 0 ? { kind: \"not-found\", itemId: safeId } : { kind: \"ok\", itemId: safeId };\n },\n );\n if (outcome.kind === \"ok\" && opts.slug) publishCollectionChange(collectionChangePayload({ slug: opts.slug, ids: [safeId], op: \"delete\" }, opts.publishRoot));\n return outcome;\n}\n\n/** Best-effort full WAL checkpoint so the MAIN db file alone is a\n * complete snapshot (committed pages in `<db>-wal` are folded in and the\n * WAL truncated). Used by `deleteCollection` before archiving. Returns\n * false on any failure (runtime without node:sqlite, locked db, missing\n * file) — the caller then archives the sidecar files alongside the db so\n * no committed data is lost either way. */\nexport async function checkpointSqliteDatabase(absPath: string): Promise<boolean> {\n try {\n const { DatabaseSync } = await loadSqlite();\n const database = new DatabaseSync(absPath);\n try {\n database.exec(\"PRAGMA wal_checkpoint(TRUNCATE)\");\n } finally {\n database.close();\n }\n return true;\n } catch {\n return false;\n }\n}\n\n/** A `storage: sqlite` store over `collection.storageFile`. A schema whose\n * `storageFile` failed to resolve yields a read-only EMPTY store rather\n * than a writable one — same fail-closed rule as the CSV store. */\nexport function sqliteStoreFor(collection: LoadedCollection, opts: IoOptions): CollectionStore {\n const file = collection.storageFile;\n const key = collection.schema.primaryKey;\n const slug = opts.slug ?? collection.slug;\n const root = (): string => opts.workspaceRoot ?? getWorkspaceRoot();\n // The EXPLICIT root (undefined when the caller relied on the host default) —\n // `root()` has already collapsed that distinction, and the change payload\n // must report only a root the caller actually named.\n const publishRoot = opts.workspaceRoot;\n if (file === undefined) {\n return {\n capabilities: { writable: false, nativeQuery: false, nativePaging: false },\n list: () => Promise.resolve([]),\n page: () => Promise.resolve({ items: [], total: 0, truncated: false }),\n read: () => Promise.resolve(null),\n };\n }\n return {\n capabilities: { writable: true, nativeQuery: false, nativePaging: true },\n list: () => sqliteList(file, root()),\n page: (pageOpts = {}) => sqlitePage(file, key, pageOpts, root()),\n read: (itemId: string) => sqliteRead(file, itemId, root()),\n write: (itemId: string, item: CollectionItem, writeOpts: WriteOptions = {}) =>\n sqliteWrite(file, itemId, item, { workspaceRoot: root(), publishRoot, slug, refuseOverwrite: writeOpts.refuseOverwrite }),\n delete: (itemId: string) => sqliteDelete(file, itemId, { workspaceRoot: root(), publishRoot, slug }),\n // One db file holds every record, so an event can't name a record. The\n // sidecars count as hits: sqlite writes land in `<db>-wal` first, and a\n // change that only touched the WAL is still a change.\n watch: async (onChange) =>\n closerFor(\n await watchSingleFile(\n file,\n (base, name) => name.startsWith(base),\n () => onChange({ kind: \"collection\" }),\n ),\n ),\n };\n}\n","// Storage abstraction over a collection's records — the one seam where\n// \"where do the rows come from\" is decided. Implementations, selected by\n// the schema's storage kind (`storageKindFor`) through the factory\n// registry below:\n//\n// - file store (\"file\"): the classic `<dataDir>/<itemId>.json` records\n// (io.ts), writable through the governed write paths;\n// - CSV store (\"csv\", csvStore.ts): the rows of an external `dataSource`\n// file, queried through DuckDB — READ-ONLY by definition;\n// - SQLite store (\"sqlite\", sqliteStore.ts): records in a single\n// node:sqlite database file — writable, native paging.\n//\n// Reads AND writes go through `storeFor(...)`. Writability is encoded by\n// PRESENCE: `write`/`delete` exist only on writable stores, so \"write\n// through a read-only store\" is a type error, not a runtime surprise —\n// entry points refuse with `readOnlyRefusal` when the methods are absent.\n// (`io.ts#writeItem`/`deleteItem` remain the file-store implementation and\n// the change-event choke point; only callers WITHOUT a `LoadedCollection`\n// in hand — e.g. `spawn.ts` — still call them directly.)\n//\n// BACKWARD COMPATIBILITY — read before evolving this interface.\n// This store is INTERNAL and may change shape, but two user-facing\n// contracts built on top of it are effectively FROZEN, because they are\n// consumed by LLM-authored custom-view HTML files that already live in\n// users' workspaces (`data/skills/*/views/*.html`, `feeds/*/views/*.html`).\n// Those files cannot be migrated centrally — there is no registry of them,\n// and users expect a view authored months ago to keep working:\n//\n// - the desktop view-data HTTP surface (`server/api/routes/collections.ts`:\n// GET `?fields=`/`?ids=`, PUT items, POST /query, POST /actions/<id>,\n// response shapes, error semantics) as documented in\n// `packages/core/assets/helps/custom-view.md`;\n// - the remote-view bridge (`../../remote-view/index.ts`: `__MC_VIEW`\n// protocol, `getItems` page shape `{ items, total, offset, limit }`,\n// mutate replies) as documented in\n// `packages/core/assets/helps/custom-view-remote.md`.\n//\n// Any storage-virtualization work (new backends, paging, capability\n// changes) must be invisible at those two surfaces: evolve them by\n// ADDITIVE, backward-compatible supersets only — never rename/repurpose\n// params or message types, never change existing response shapes, never\n// let a new backend alter what an existing view observes.\n\nimport { fieldText } from \"../core/fieldText\";\nimport type { CollectionItem, CollectionStorageKind } from \"../core/schema\";\nimport type { CollectionQuery } from \"../core/queryZ\";\nimport { isReadOnlySchema, storageKindFor } from \"../core/schema\";\nimport type { LoadedCollection } from \"./discoveredCollection\";\nimport { deleteItem, listItems, readItem, writeItem, type DeleteItemResult, type IoOptions, type WriteItemResult } from \"./io\";\nimport { csvList, csvRead, csvRunQuery } from \"./csvStore\";\nimport { sqliteStoreFor } from \"./sqliteStore\";\nimport { firestoreStoreFor } from \"./firestoreStore\";\nimport { pageFromFullRead, type ListOptions, type ListPage, type WriteOptions } from \"./storePage\";\nimport { closerFor, watchDirectory, watchSingleFile } from \"./watchFs\";\n\n// The pure paging/projection primitives live in storePage.ts (so backend\n// modules can share them without an import cycle); re-exported here to\n// keep the public surface where it has always been.\nexport { pageFromFullRead, projectItemFields, type ListOptions, type ListPage, type WriteOptions } from \"./storePage\";\n\nexport interface CollectionStoreCapabilities {\n readonly writable: boolean;\n /** Native aggregation engine for the structured DSL (`core/queryZ.ts`).\n * False ⇒ `query` is absent; the engine-level fallback (enrich →\n * JSONL → DuckDB, `queryRunner.ts`) answers aggregations instead. */\n readonly nativeQuery: boolean;\n /** True when `page` resolves offset/limit inside the backend. False ⇒\n * `page` is emulated (full read, then slice) — same result, no saving. */\n readonly nativePaging: boolean;\n}\n\n/** The storage contract every backend must satisfy (verified by the shared\n * contract test suite, `test/workspace/collections/test_storeContract.ts`):\n *\n * 1. STABLE ORDER — `page` walks a documented deterministic order (file\n * store: lexicographic by record id; CSV store: file row order; SQLite\n * store: `ORDER BY id`), so `offset`-paging never skips or repeats\n * records between calls. Sorting by arbitrary fields is NOT the\n * store's job.\n * 2. IDS — minting/resolving record ids is the store's job (the CSV\n * store's `id0x…` encoding stays inside it); `read` resolves every id\n * `list`/`page` returned.\n * 3. CONTAINMENT — a store never serves data from outside the workspace;\n * symlink/realpath defenses are each implementation's obligation\n * (io.ts is the reference).\n * 4. HONEST AGGREGATION — `query`, when present, is computed over the\n * WHOLE data set, never from a capped read. */\n/** What a store reports when its records changed underneath it.\n *\n * Two granularities, because backends genuinely differ: a per-record file\n * store knows WHICH record changed, while a single-artifact backend (one\n * CSV, one db file, one remote snapshot) only knows THAT something did.\n * Naming the difference here is what lets the watcher react uniformly\n * instead of branching on the backend. */\nexport type StoreChange = { readonly kind: \"item\"; readonly itemId: string } | { readonly kind: \"collection\" };\n\nexport type StoreChangeListener = (change: StoreChange) => void;\n\n/** Detaches a `watch` subscription. */\nexport type StoreUnsubscribe = () => void;\n\nexport interface CollectionStore {\n readonly capabilities: CollectionStoreCapabilities;\n /** Every record, in the store's stable order. CSV store: capped at\n * `MAX_CSV_ROWS` (see csvStore.ts). Prefer `page` in new code. */\n list: () => Promise<CollectionItem[]>;\n /** One page of records — offset/limit/projection over the stable order. */\n page: (opts?: ListOptions) => Promise<ListPage>;\n /** One record by id, or null when missing/invalid. */\n read: (itemId: string) => Promise<CollectionItem | null>;\n /** Aggregation over the WHOLE data set (the structured DSL,\n * `core/queryZ.ts`) — present only on stores with a native query\n * engine (the CSV store). Absent ⇒ use the engine-level fallback\n * (`runCollectionQuery`), never emulate ad hoc. */\n query?: ((query: CollectionQuery) => Promise<Record<string, unknown>[]>) | undefined;\n /** Present ONLY when `capabilities.writable` — absence IS the read-only\n * refusal (surface it with `readOnlyRefusal`). A successful write/delete\n * publishes a collection-change event: the store always threads the\n * collection's slug into the publish hook, so no writer can forget it. */\n write?: ((itemId: string, item: CollectionItem, opts?: WriteOptions) => Promise<WriteItemResult>) | undefined;\n delete?: ((itemId: string) => Promise<DeleteItemResult>) | undefined;\n /** Subscribe to changes made OUTSIDE this process — a file edited by the\n * agent, a CSV replaced by the user, a db written by another tool.\n *\n * Resolves to an unsubscribe function, or to `null` when the backend\n * could not arm the watch (no inotify watches left, an unreadable\n * directory). `null` is NOT the same as absent `watch`: absent means the\n * backend never reports changes and the caller should settle for its\n * periodic pass, while `null` means this attempt failed and mounting\n * should be retried. Swallowing the difference strands a collection on\n * stale data until the process restarts.\n *\n * This is the store's job because only it knows where its bytes live and\n * what its own change events look like (which paths to watch, which\n * filenames are noise, whether an atomic replace surfaces as two events).\n * Absorbing that noise — debouncing a replace into one report — belongs\n * here too. What to DO about a change (reconcile bells, sweep, publish)\n * is the watcher's policy and is deliberately NOT expressed here.\n *\n * Absent ⇒ the backend cannot report external changes; the caller falls\n * back to its periodic pass. */\n watch?: (onChange: StoreChangeListener) => Promise<StoreUnsubscribe | null>;\n}\n\n/** The file store's stable order: lexicographic by record id (codepoint\n * compare — locale-independent). `listItems` returns readdir order, which\n * is filesystem-dependent; paging needs determinism. */\nfunction sortByRecordId(items: CollectionItem[], primaryKey: string): CollectionItem[] {\n return [...items].sort((left, right) => {\n const leftId = fieldText(left[primaryKey]);\n const rightId = fieldText(right[primaryKey]);\n if (leftId < rightId) return -1;\n return leftId > rightId ? 1 : 0; // 0 on equality — a comparator that never ties breaks sort's contract\n });\n}\n\n/** True when the collection accepts UI/tool writes. A `dataSource`\n * collection is read-only: updates happen by editing/replacing the\n * data file itself. Every write entry point checks this BEFORE calling\n * `writeItem`/`deleteItem` — server-enforced, not just UI-hidden. */\nexport function collectionWritable(collection: Pick<LoadedCollection, \"schema\">): boolean {\n return !isReadOnlySchema(collection.schema);\n}\n\n/** The one-line refusal write paths surface (HTTP 405 / MCP error text). */\nexport function readOnlyRefusal(slug: string): string {\n return `collection '${slug}' is read-only (backed by an external dataSource) — update the data file itself instead`;\n}\n\n/** A `dataSource` store over `file` (CSV row order; DuckDB-native query).\n * A schema whose `dataSourceFile` failed to resolve yields a read-only\n * EMPTY store rather than falling back to the (writable) file store — a\n * half-loaded read-only collection must never become writable. */\nfunction csvStoreFor(collection: LoadedCollection, opts: IoOptions): CollectionStore {\n const file = collection.dataSourceFile;\n const key = collection.schema.primaryKey;\n const listAll = () => (file === undefined ? Promise.resolve({ items: [], truncated: false }) : csvList(file, key, opts.workspaceRoot));\n return {\n capabilities: { writable: false, nativeQuery: true, nativePaging: false },\n list: () => listAll().then((result) => result.items),\n page: (pageOpts = {}) => listAll().then((result) => pageFromFullRead(result.items, pageOpts, key, result.truncated)),\n read: (itemId: string) => (file === undefined ? Promise.resolve(null) : csvRead(file, key, itemId, opts.workspaceRoot)),\n query: (query: CollectionQuery) => (file === undefined ? Promise.resolve([]) : csvRunQuery(file, key, query, opts.workspaceRoot)),\n // One file holds every row, so an event can't name a record — only that\n // the set changed. `watchSingleFile` watches the parent dir (an atomic\n // replace swaps the inode) and debounces the 2-3 events a replace emits.\n ...(file === undefined\n ? {}\n : {\n watch: async (onChange) =>\n closerFor(\n await watchSingleFile(\n file,\n () => false,\n () => onChange({ kind: \"collection\" }),\n ),\n ),\n }),\n };\n}\n\n/** The classic file store over `<dataDir>/<itemId>.json` records. */\nfunction fileStoreFor(collection: LoadedCollection, opts: IoOptions): CollectionStore {\n const key = collection.schema.primaryKey;\n const ioOpts: IoOptions = { ...opts, slug: opts.slug ?? collection.slug };\n return {\n capabilities: { writable: true, nativeQuery: false, nativePaging: false },\n list: () => listItems(collection.dataDir, opts),\n page: async (pageOpts = {}) => pageFromFullRead(sortByRecordId(await listItems(collection.dataDir, opts), key), pageOpts, key, false),\n read: (itemId: string) => readItem(collection.dataDir, itemId, opts),\n write: (itemId: string, item: CollectionItem, writeOpts: WriteOptions = {}) =>\n writeItem(collection.dataDir, itemId, item, { ...ioOpts, refuseOverwrite: writeOpts.refuseOverwrite }),\n delete: (itemId: string) => deleteItem(collection.dataDir, itemId, ioOpts),\n // One file per record, so an event names the record that changed —\n // reported at `item` granularity, which is what lets the watcher\n // reconcile just that record instead of the whole collection. Dot-\n // prefixed names are skipped: atomic writes, OS metadata and editor swap\n // files are not records.\n watch: async (onChange) =>\n closerFor(\n await watchDirectory(\n collection.dataDir,\n (name) => name.endsWith(\".json\") && !name.startsWith(\".\"),\n (filename) => onChange(filename === null ? { kind: \"collection\" } : { kind: \"item\", itemId: filename.slice(0, -\".json\".length) }),\n ),\n ),\n };\n}\n\nexport type CollectionStoreFactory = (collection: LoadedCollection, opts: IoOptions) => CollectionStore;\n\n// The store factory registry (plans/done/refactor-storage-virtualization.md,\n// Stage 3): schema storage kind → implementation. Factories live in CORE\n// (dependency-direction rule — never plugin-registered); a new backend is\n// one factory + a `StorageZ` variant + a pass of the contract test suite.\nconst storeFactories = new Map<CollectionStorageKind, CollectionStoreFactory>([\n [\"file\", fileStoreFor],\n [\"csv\", csvStoreFor],\n [\"sqlite\", sqliteStoreFor],\n [\"firestore\", firestoreStoreFor],\n]);\n\n/** Pick the store implementation for a discovered collection via the\n * factory registry. An unknown kind cannot normally reach here (the\n * schema's `StorageZ` union gates it), so the throw is a loud invariant\n * breach, not a user-facing path. */\nexport function storeFor(collection: LoadedCollection, opts: IoOptions = {}): CollectionStore {\n const kind = storageKindFor(collection.schema);\n const factory = storeFactories.get(kind);\n if (!factory) throw new Error(`no store factory registered for storage kind '${kind}'`);\n return factory(collection, opts);\n}\n","// Pure helpers for `kind: \"mutate\"` actions (plan step ④ of\n// plans/done/collection-ontology.md): the `$params.<name>` reference syntax\n// used in a mutate action's `set` map. ONE parser shared by the schema\n// refines (`schemaZ.ts` validates that every reference names a declared\n// param) and the server executor (`server/mutate.ts` resolves them\n// against the submitted form values) — the two must agree on what a\n// reference is. Zod-free and I/O-free.\n\n/** A `set` value starting with this prefix reads the named form param\n * instead of being written literally. */\nexport const PARAM_REF_PREFIX = \"$params.\";\n\n/** The param name a `set` value references, or null when the value is a\n * literal (non-strings can never be references). A bare/empty prefix\n * (`\"$params.\"`) returns the empty string — the schema refine rejects\n * it as an undeclared param, never silently treats it as a literal. */\nexport function paramRefName(value: unknown): string | null {\n if (typeof value !== \"string\" || !value.startsWith(PARAM_REF_PREFIX)) return null;\n return value.slice(PARAM_REF_PREFIX.length);\n}\n\n/** Resolve a mutate action's `set` map against the submitted params:\n * literals pass through, `$params.<name>` reads the param value. An\n * ABSENT referenced param omits the key entirely (merge semantics —\n * the stored value survives), mirroring how the record form omits\n * empty optionals rather than writing empty strings. */\nexport function resolveMutateSet(set: Record<string, string | number | boolean>, params: Record<string, unknown>): Record<string, unknown> {\n const resolved: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(set)) {\n const ref = paramRefName(value);\n if (ref === null) {\n resolved[key] = value;\n continue;\n }\n const paramValue = params[ref];\n if (paramValue !== undefined && paramValue !== null && paramValue !== \"\") resolved[key] = paramValue;\n }\n return resolved;\n}\n","// Cross-field rules for the collection schema — the checks a single field\n// cannot make because it cannot see its siblings.\n//\n// Each one is a named predicate over a parsed-but-unvalidated schema, returning\n// true when the schema is ACCEPTABLE. They live here rather than as anonymous\n// lambdas inside `./schemaZ`'s refine chain so each rule can be unit-tested on\n// its own, and so the chain reads as a list of rule names instead of 500 lines\n// of inline logic.\n//\n// Every rule guards a misconfiguration that would otherwise fail SILENTLY at\n// runtime: a completion bell that never rings, a spawn that fans out forever, a\n// currency that mislabels amounts. That is why they are hard errors at load\n// rather than warnings.\n//\n// The type-only import of `CollectionSchemaInput` is erased at emit, so the\n// module graph stays acyclic at runtime: schemaZ → schemaRules → schema.\n\nimport { fieldTextOrNull } from \"./fieldText\";\nimport { isSafeSlug, isSafeRecordId } from \"./ids\";\nimport { paramRefName } from \"./mutateAction\";\nimport { COMPUTED_TYPES } from \"./schema\";\nimport type { CollectionSchemaInput } from \"./schemaZ\";\n\ntype Schema = CollectionSchemaInput;\ntype Fields = Schema[\"fields\"];\n\n// A field pointer names a REAL declared field only when it is an OWN key of\n// `fields`. A bare `fields[name]` reaches inherited Object.prototype members\n// (`constructor`, `__proto__`, `toString`), so an LLM-authored pointer like\n// `completionField: \"constructor\"` would resolve to a function and pass every\n// \"names a declared field\" check, then misfire silently at runtime (#2318).\nconst declaredField = (fields: Fields, name: string): Fields[string] | undefined => (Object.hasOwn(fields, name) ? fields[name] : undefined);\n\n// The calendar anchor/end fields accept either a date-only or a datetime\n// field (the latter carries the clock for the day view).\nconst isDateLike = (type: string | undefined): boolean => type === \"date\" || type === \"datetime\";\n\n// `calendarTimeField` parses a free-form time string, so it must name a\n// string-backed field — a number/enum/date column has no time-range text.\nconst isTimeStringField = (type: string | undefined): boolean => type === \"string\" || type === \"text\";\n\n// Field types that can hold a currency code string. A `currencyField`\n// pointer must resolve to one of these — pointing at a number / boolean\n// / table would never yield a usable ISO code.\nconst CODE_FIELD_TYPES = new Set([\"string\", \"text\", \"enum\"]);\n\nconst namesStoredField = (fields: Fields, name: string, primaryKey: string): boolean => {\n const target = declaredField(fields, name);\n return target !== undefined && !COMPUTED_TYPES.has(target.type) && name !== primaryKey;\n};\n\nconst hasUniqueIds = (entries: { id: string }[] | undefined): boolean =>\n entries === undefined || new Set(entries.map((entry) => entry.id)).size === entries.length;\n\n// ---------------------------------------------------------------------------\n// Storage declaration\n// ---------------------------------------------------------------------------\n\n/** Exactly one storage declaration: native records need `dataPath`, an external\n * data file needs `dataSource`, an alternative backend needs `storage`. Zero\n * (nowhere to read) and several (ambiguous which wins) are equally\n * meaningless — fail loudly at load instead of picking silently. */\nexport function declaresExactlyOneStore(schema: Schema): boolean {\n return [schema.dataPath, schema.dataSource, schema.storage].filter((declared) => declared !== undefined).length === 1;\n}\n\n// NOTE: `storage` collections support the full write machinery (`spawn` /\n// `completionField` / `triggerField` / `singleton` / `ingest` / mutate actions)\n// — spawn and the watcher reconcilers go through the CollectionStore seam, and\n// a db-file watcher drives their reconciliation (collection-watchers/watcher.ts).\n/** A `dataSource` collection is read-only by definition, so schema-level write\n * machinery can never fire: `singleton` pins CREATES, `ingest` REFILLS\n * records, `spawn` WRITES successor records. Rejecting them at validation\n * kills whole classes of writes before any runtime guard. */\nexport function dataSourceDeclaresNoWriteMachinery(schema: Schema): boolean {\n if (schema.dataSource === undefined) return true;\n return schema.singleton === undefined && schema.ingest === undefined && schema.spawn === undefined && schema.googleCalendar === undefined;\n}\n\n/** Same rule for declarative host writes: a mutate action writes the record\n * it's invoked on, which a read-only collection has no business doing. */\nexport function dataSourceDeclaresNoMutateAction(schema: Schema): boolean {\n if (schema.dataSource !== undefined) {\n return [...(schema.actions ?? []), ...(schema.collectionActions ?? [])].every((action) => action.kind !== \"mutate\");\n }\n return true;\n}\n\n// ---------------------------------------------------------------------------\n// Actions\n// ---------------------------------------------------------------------------\n\n/** Action ids must be unique so the dispatch route resolves unambiguously. */\nexport function actionIdsAreUnique(schema: Schema): boolean {\n return hasUniqueIds(schema.actions);\n}\n\n/** Collection-level action ids must likewise be unique. */\nexport function collectionActionIdsAreUnique(schema: Schema): boolean {\n return hasUniqueIds(schema.collectionActions);\n}\n\n/** A mutate action's `set` writes real STORED fields: a typo'd key would write\n * a stray value forever, a computed/projected field is never persisted, and\n * the primaryKey is the filename (renaming is not a mutation). */\nexport function mutateSetKeysNameStoredFields(schema: Schema): boolean {\n return (schema.actions ?? []).every(\n (action) => action.kind !== \"mutate\" || Object.keys(action.set).every((key) => namesStoredField(schema.fields, key, schema.primaryKey)),\n );\n}\n\n/** Every `$params.<name>` reference in `set` must name a declared param — an\n * undeclared one would silently no-op the assignment. */\nexport function mutateParamRefsAreDeclared(schema: Schema): boolean {\n return (schema.actions ?? []).every(\n (action) =>\n action.kind !== \"mutate\" ||\n Object.values(action.set).every((value) => {\n const ref = paramRefName(value);\n return ref === null || (action.params ?? {})[ref] !== undefined;\n }),\n );\n}\n\n/** A collection-level action has no record to write. */\nexport function collectionActionsAreNotMutate(schema: Schema): boolean {\n return (schema.collectionActions ?? []).every((action) => action.kind !== \"mutate\");\n}\n\n// ---------------------------------------------------------------------------\n// Singleton, currency\n// ---------------------------------------------------------------------------\n\n/** The singleton value becomes a record id (and thus a `<id>.json` filename),\n * so it must satisfy the SAME record-id rule the write path enforces —\n * otherwise the create form would lock the primary key to a value the POST\n * route then rejects, making the collection impossible to initialize. */\nexport function singletonIsAValidRecordId(schema: Schema): boolean {\n return schema.singleton === undefined || isSafeRecordId(schema.singleton);\n}\n\n// `type` is carried on both levels even though this helper never reads it:\n// without a required property these become weak types, and a field variant that\n// declares no `currencyField` would no longer be assignable.\ninterface CurrencyBearingField {\n type: string;\n currencyField?: string | undefined;\n of?: Record<string, { type: string; currencyField?: string | undefined }> | undefined;\n}\n\n// Every `currencyField` declared anywhere in the schema — top-level fields and\n// a table's `of` sub-fields. Sub-field money cells resolve currency against the\n// TOP-LEVEL record (rows carry no currency), so their pointers are validated\n// against the top-level field set too.\nfunction collectCurrencyFieldRefs(fields: Record<string, CurrencyBearingField>): string[] {\n const refs: string[] = [];\n for (const field of Object.values(fields)) {\n if (typeof field.currencyField === \"string\" && field.currencyField.length > 0) refs.push(field.currencyField);\n for (const sub of Object.values(field.of ?? {})) {\n if (typeof sub.currencyField === \"string\" && sub.currencyField.length > 0) refs.push(sub.currencyField);\n }\n }\n return refs;\n}\n\n/** A `currencyField` pointer must name a real top-level field that holds a code\n * string — a typo (`curreny`) would otherwise pass the per-field check, then\n * silently fall back to the literal / USD at render and mislabel amounts. */\nexport function currencyFieldRefsNameCodeFields(schema: Schema): boolean {\n return collectCurrencyFieldRefs(schema.fields).every((name) => CODE_FIELD_TYPES.has(declaredField(schema.fields, name)?.type ?? \"\"));\n}\n\n// ---------------------------------------------------------------------------\n// Completion tracking\n// ---------------------------------------------------------------------------\n\n/** The pair must be declared together — one without the other is meaningless:\n * the host would either never fire (no done values to compare against) or\n * never clear (no field to read).\n *\n * EXCEPTION: when `completionField` names a `flag` field, done ⇔ the flag's\n * `where` matches, so `completionDoneValues` carries no information and MUST\n * be omitted (declaring it would invite a contradictory second source of\n * truth). */\nexport function completionPairIsCoherent(schema: Schema): boolean {\n if (schema.completionField !== undefined && declaredField(schema.fields, schema.completionField)?.type === \"flag\") {\n return schema.completionDoneValues === undefined;\n }\n return (schema.completionField === undefined) === (schema.completionDoneValues === undefined);\n}\n\n/** `completionField` must name a real top-level field — a typo would silently\n * disable the notification mechanism otherwise. */\nexport function completionFieldIsDeclared(schema: Schema): boolean {\n return schema.completionField === undefined || declaredField(schema.fields, schema.completionField) !== undefined;\n}\n\n/** A flag named by `completionField` is evaluated against the RAW record — the\n * reconciler (and spawn's fallback) read items straight off disk, BEFORE any\n * `deriveAll` enrichment — so its `where` may only reference STORED fields. A\n * condition over a computed sibling would see an absent key: `ne` matches\n * vacuously, every other op reads false, and the bell would clear wrongly /\n * never. General (non-completion) flags keep the full vocabulary — the UI\n * evaluates them post-enrichment. */\nexport function completionFlagReadsOnlyStoredFields(schema: Schema): boolean {\n const spec = schema.completionField === undefined ? undefined : declaredField(schema.fields, schema.completionField);\n if (spec?.type !== \"flag\") return true;\n return spec.where.every((cond) =>\n [cond.field, ...(cond.valueFrom ? [cond.valueFrom.field] : [])].every((name) => {\n const target = declaredField(schema.fields, name);\n return target !== undefined && !COMPUTED_TYPES.has(target.type);\n }),\n );\n}\n\n// ---------------------------------------------------------------------------\n// Field pointers\n// ---------------------------------------------------------------------------\n\n/** `displayField`, like `completionField`, must name a real top-level field —\n * a typo would silently fall back to the primaryKey forever. */\nexport function displayFieldIsDeclared(schema: Schema): boolean {\n return schema.displayField === undefined || declaredField(schema.fields, schema.displayField) !== undefined;\n}\n\n/** A field's `when.field` gates its visibility against a sibling's value, so it\n * must name a real top-level field — a typo would silently keep the field\n * hidden forever (the gate never matches). */\nexport function fieldVisibilityGatesNameDeclaredFields(schema: Schema): boolean {\n return Object.values(schema.fields).every((field) => field.when === undefined || declaredField(schema.fields, field.when.field) !== undefined);\n}\n\n/** A flag's `where` reads sibling fields (both `cond.field` and a same-record\n * `valueFrom.field`), so each must name a real top-level field — a typo would\n * silently pin the flag false forever (`ne`: true forever). */\nexport function flagConditionsNameDeclaredFields(schema: Schema): boolean {\n return Object.values(schema.fields).every(\n (field) =>\n field.type !== \"flag\" ||\n field.where.every(\n (cond) =>\n declaredField(schema.fields, cond.field) !== undefined &&\n (cond.valueFrom === undefined || declaredField(schema.fields, cond.valueFrom.field) !== undefined),\n ),\n );\n}\n\n/** An `embed`'s `idField` resolves the target record id from a sibling's value,\n * so it must name a real top-level field — and one whose stored value is a\n * plain id string. Only `ref` / `string` qualify: the editor writes the picked\n * id into that field, so a non-persisted or composite type would either not\n * round-trip on save or hold no usable id. */\nexport function embedIdFieldsNameIdBearingFields(schema: Schema): boolean {\n return Object.values(schema.fields).every((field) => {\n if (field.type !== \"embed\" || field.idField === undefined) return true;\n const target = declaredField(schema.fields, field.idField);\n return target !== undefined && (target.type === \"ref\" || target.type === \"string\");\n });\n}\n\n/** The sync writes each mapped value into a declared field, and puts the Google\n * event id in the primary field — so a map key that names no field (or names\n * the primary) would silently drop data or fight the id. */\nexport function googleCalendarMapNamesStoredFields(schema: Schema): boolean {\n if (schema.googleCalendar === undefined) return true;\n return Object.keys(schema.googleCalendar.map).every((key) => namesStoredField(schema.fields, key, schema.primaryKey));\n}\n\n// ---------------------------------------------------------------------------\n// Toggle projection\n// ---------------------------------------------------------------------------\n\n/** A `toggle` field projects an `enum` field: its `field` must name a real\n * top-level enum, and `onValue` / `offValue` must be members of that enum's\n * `values` — otherwise toggling would write a value outside the closed set\n * (and never appear \"checked\"). */\nexport function togglesProjectValidEnums(schema: Schema): boolean {\n const { fields } = schema;\n for (const spec of Object.values(fields)) {\n if (spec.type !== \"toggle\") continue;\n const target = declaredField(fields, spec.field);\n if (!target || target.type !== \"enum\") return false;\n const allowed = new Set(target.values);\n if (!allowed.has(spec.onValue) || !allowed.has(spec.offValue)) return false;\n }\n return true;\n}\n\n// ---------------------------------------------------------------------------\n// Trigger\n// ---------------------------------------------------------------------------\n\n/** `triggerField` requires the completion pair: the time gate only suppresses\n * the *completion* bell until the date, and the bell still clears via\n * `completionDoneValues`. Without completion there is no bell to gate. */\nexport function triggerFieldRequiresCompletion(schema: Schema): boolean {\n return schema.triggerField === undefined || schema.completionField !== undefined;\n}\n\n/** `triggerField` must name a real `date` field — the gate parses its value as\n * `YYYY-MM-DD`; any other type can't be compared to the clock. */\nexport function triggerFieldIsADateField(schema: Schema): boolean {\n return schema.triggerField === undefined || declaredField(schema.fields, schema.triggerField)?.type === \"date\";\n}\n\n/** `triggerLeadDays` only means something relative to a trigger date. */\nexport function triggerLeadDaysRequiresTriggerField(schema: Schema): boolean {\n return schema.triggerLeadDays === undefined || schema.triggerField !== undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Spawn\n// ---------------------------------------------------------------------------\n\n/** `spawn` advances `triggerField` to compute the successor's trigger date, so\n * the schema must declare one. */\nexport function spawnRequiresTriggerField(schema: Schema): boolean {\n return schema.spawn === undefined || schema.triggerField !== undefined;\n}\n\n/** `spawn.when.field` must name a real top-level field — a typo would silently\n * never match. */\nexport function spawnWhenFieldIsDeclared(schema: Schema): boolean {\n return schema.spawn?.when === undefined || declaredField(schema.fields, schema.spawn.when.field) !== undefined;\n}\n\n/** Every `spawn.carry` entry must name a real top-level field — a typo would\n * silently never copy. */\nexport function spawnCarryEntriesAreDeclared(schema: Schema): boolean {\n return (schema.spawn?.carry ?? []).every((name) => declaredField(schema.fields, name) !== undefined);\n}\n\n/** A successor must NOT be born already matching its own spawn predicate — it\n * would re-spawn on its first reconcile, fanning out into an unbounded chain\n * of records. The predicate field/values are `spawn.when` when given, else the\n * completion-done pair. The successor's value for that field is `set[field]`\n * if set, else the carried source value (which matched, by definition, when\n * the spawn fired) if carried, else absent (safe). */\nexport function spawnSuccessorStartsInert(schema: Schema): boolean {\n const { spawn } = schema;\n if (!spawn) return true;\n const field = spawn.when?.field ?? schema.completionField;\n const values = spawn.when?.in ?? schema.completionDoneValues;\n if (!field || !values) return true; // predicate not evaluable — other rules cover this\n if (spawn.set && Object.prototype.hasOwnProperty.call(spawn.set, field)) {\n return !values.includes(String(spawn.set[field])); // `set` wins over `carry`\n }\n return !(spawn.carry ?? []).includes(field); // carried ⇒ inherits the matching value\n}\n\n/** `spawnSuccessorStartsInert` cannot see through a flag's `where` (the\n * predicate would need full record evaluation against `set`/`carry`). So a\n * schema whose completion is flag-form may only spawn with an explicit\n * `spawn.when` — which that check CAN evaluate. */\nexport function flagCompletionSpawnDeclaresWhen(schema: Schema): boolean {\n return schema.spawn === undefined || schema.spawn.when !== undefined || declaredField(schema.fields, schema.completionField ?? \"\")?.type !== \"flag\";\n}\n\n// Resolve the field-driven arm of `spawn.every`, or null when spawn is absent\n// or its `every` is the literal arm. Lets each rule below short-circuit\n// (return valid) without re-checking the discriminant.\nfunction fieldDrivenSpawnEvery(schema: Schema) {\n const every = schema.spawn?.every;\n if (!every || !(\"fromField\" in every)) return null;\n return every;\n}\n\n/** §4.1 — `fromField` must name a real top-level `enum` field. The `map` keys\n * are only meaningful against a closed value set, and the field renders as a\n * form `<select>`; a non-enum target has no finite values to validate. */\nexport function fieldDrivenFromFieldIsEnum(schema: Schema): boolean {\n const driven = fieldDrivenSpawnEvery(schema);\n if (!driven) return true;\n return declaredField(schema.fields, driven.fromField)?.type === \"enum\";\n}\n\n/** §4.2 — `map` keys must EXACTLY cover the enum's `values` (no missing keys —\n * a record could pick an unmapped frequency and silently stall; no extra keys\n * — a stale map outliving an enum edit). */\nexport function fieldDrivenMapCoversValues(schema: Schema): boolean {\n const driven = fieldDrivenSpawnEvery(schema);\n if (!driven) return true;\n const target = declaredField(schema.fields, driven.fromField);\n if (target?.type !== \"enum\") return true; // §4.1 reports the type error\n const values = new Set<string>(target.values);\n const keys = Object.keys(driven.map);\n return keys.length === values.size && keys.every((key) => values.has(key));\n}\n\n/** §4.5 — `fromField` must reach the successor (via `carry` or `set`);\n * otherwise the successor loses its frequency and the NEXT spawn along the\n * chain can't resolve an interval, silently halting the recurrence.\n *\n * `set` writes a FIXED value, so it must itself be a key of `map` (else the\n * successor is born with an unresolvable driver and `resolveEvery` skips it —\n * the exact silent-halt §4.5 exists to prevent). `carry` copies the source's\n * own value, which — for a record that matched the spawn — is one of the\n * enum's values, all of which `map` covers by §4.2; so a carried driver is\n * always resolvable and needs no value check here. */\nexport function fieldDrivenFromFieldCarried(schema: Schema): boolean {\n const driven = fieldDrivenSpawnEvery(schema);\n if (!driven) return true;\n const { carry, set } = schema.spawn ?? {};\n if (set && Object.prototype.hasOwnProperty.call(set, driven.fromField)) {\n const raw = set[driven.fromField];\n if (raw === undefined || raw === null || raw === \"\") return false;\n const key = fieldTextOrNull(raw);\n return key !== null && Object.prototype.hasOwnProperty.call(driven.map, key);\n }\n return (carry ?? []).includes(driven.fromField);\n}\n\n// ---------------------------------------------------------------------------\n// Calendar\n// ---------------------------------------------------------------------------\n\n/** `calendarField` must name a real `date`/`datetime` field — the calendar view\n * parses its value to place records on the month grid (a `datetime` anchor\n * also carries the clock for the day view). */\nexport function calendarFieldIsDateLike(schema: Schema): boolean {\n return schema.calendarField === undefined || isDateLike(declaredField(schema.fields, schema.calendarField)?.type);\n}\n\n/** `calendarEndField` marks the end of a multi-day span, so it only means\n * something alongside a start anchor. */\nexport function calendarEndFieldRequiresCalendarField(schema: Schema): boolean {\n return schema.calendarEndField === undefined || schema.calendarField !== undefined;\n}\n\n/** `calendarEndField` must also name a real `date`/`datetime` field — same parse. */\nexport function calendarEndFieldIsDateLike(schema: Schema): boolean {\n return schema.calendarEndField === undefined || isDateLike(declaredField(schema.fields, schema.calendarEndField)?.type);\n}\n\n/** `calendarTimeField` places records on the day view, so it only means\n * something alongside a start anchor. */\nexport function calendarTimeFieldRequiresCalendarField(schema: Schema): boolean {\n return schema.calendarTimeField === undefined || schema.calendarField !== undefined;\n}\n\n/** `calendarTimeField` must name a real top-level field (a free-form time\n * string the day view parses). */\nexport function calendarTimeFieldIsDeclared(schema: Schema): boolean {\n return schema.calendarTimeField === undefined || declaredField(schema.fields, schema.calendarTimeField) !== undefined;\n}\n\n/** …and that field must be string-backed — the day view parses its value as a\n * time string, so a number/enum/date column can't drive it. */\nexport function calendarTimeFieldIsStringBacked(schema: Schema): boolean {\n return schema.calendarTimeField === undefined || isTimeStringField(declaredField(schema.fields, schema.calendarTimeField)?.type);\n}\n\n/** `kanbanField` must name a real `enum` field — the board groups records into\n * one column per declared enum value; any other type has no closed set of\n * columns to group by. */\nexport function kanbanFieldIsAnEnum(schema: Schema): boolean {\n return schema.kanbanField === undefined || declaredField(schema.fields, schema.kanbanField)?.type === \"enum\";\n}\n\n// ---------------------------------------------------------------------------\n// notifyWhen, custom views\n// ---------------------------------------------------------------------------\n\n/** `notifyWhen` narrows the completion bell, so it only means something with\n * completion tracking. */\nexport function notifyWhenRequiresCompletion(schema: Schema): boolean {\n return schema.notifyWhen === undefined || schema.completionField !== undefined;\n}\n\n/** `notifyWhen.field` must name a real top-level field. */\nexport function notifyWhenFieldIsDeclared(schema: Schema): boolean {\n return schema.notifyWhen === undefined || declaredField(schema.fields, schema.notifyWhen.field) !== undefined;\n}\n\n/** Every custom view `id` must be a valid slug — it doubles as the view-mode\n * selector key (`custom:<id>`) and the capability-token clamp key, both of\n * which expect a path-safe token. */\nexport function viewIdsAreSlugs(schema: Schema): boolean {\n return schema.views === undefined || schema.views.every((view) => isSafeSlug(view.id));\n}\n\n/** Custom view ids must be unique so the selector + token clamp resolve\n * unambiguously. */\nexport function viewIdsAreUnique(schema: Schema): boolean {\n return hasUniqueIds(schema.views);\n}\n","// The zod SINGLE SOURCE OF TRUTH for the collection schema contract.\n//\n// Every TypeScript type in `./schema` is derived from these definitions via\n// `z.infer` (type-only imports, erased at emit) — there is no hand-written\n// mirror to drift. Field specs are a DISCRIMINATED UNION on `type` (and\n// `ingest` on `kind`): each variant declares exactly the keys it owns, so an\n// unknown key is stripped per-variant, a new field type is a new union\n// member (not another optional key + refine on a flat bag), and error\n// messages name the variant that failed.\n//\n// This module is ISOMORPHIC — zod plus the pure predicates in `./ids` /\n// `./templatePath`, no node built-ins — but it is deliberately NOT exported\n// through the browser barrel (`../index`): browser code imports the derived\n// TYPES from `./schema` (type-only ⇒ no zod in the bundle) and the server\n// imports the validators through `../server` (discovery re-exports\n// `CollectionSchemaZ`). Runtime imports here point only at `./schema`'s\n// consts, so the module graph stays acyclic: schemaZ → schema.\n\nimport { isRecord, isUnknownArray } from \"@mulmoclaude/common\";\nimport { z } from \"zod\";\nimport { isSafeSlug } from \"./ids\";\nimport { isSafeActionTemplatePath, isSafeCustomViewI18nPath, isSafeCustomViewPath } from \"./templatePath\";\nimport { INGEST_KINDS, AGENT_INGEST_KIND, FEED_SCHEDULES } from \"./schema\";\nimport {\n actionIdsAreUnique,\n calendarEndFieldIsDateLike,\n calendarEndFieldRequiresCalendarField,\n calendarFieldIsDateLike,\n calendarTimeFieldIsDeclared,\n calendarTimeFieldIsStringBacked,\n calendarTimeFieldRequiresCalendarField,\n collectionActionIdsAreUnique,\n collectionActionsAreNotMutate,\n completionFieldIsDeclared,\n completionFlagReadsOnlyStoredFields,\n completionPairIsCoherent,\n currencyFieldRefsNameCodeFields,\n dataSourceDeclaresNoMutateAction,\n dataSourceDeclaresNoWriteMachinery,\n declaresExactlyOneStore,\n displayFieldIsDeclared,\n embedIdFieldsNameIdBearingFields,\n fieldDrivenFromFieldCarried,\n fieldDrivenFromFieldIsEnum,\n fieldDrivenMapCoversValues,\n fieldVisibilityGatesNameDeclaredFields,\n flagCompletionSpawnDeclaresWhen,\n flagConditionsNameDeclaredFields,\n googleCalendarMapNamesStoredFields,\n kanbanFieldIsAnEnum,\n mutateParamRefsAreDeclared,\n mutateSetKeysNameStoredFields,\n notifyWhenFieldIsDeclared,\n notifyWhenRequiresCompletion,\n singletonIsAValidRecordId,\n spawnCarryEntriesAreDeclared,\n spawnRequiresTriggerField,\n spawnSuccessorStartsInert,\n spawnWhenFieldIsDeclared,\n togglesProjectValidEnums,\n triggerFieldIsADateField,\n triggerFieldRequiresCompletion,\n triggerLeadDaysRequiresTriggerField,\n viewIdsAreSlugs,\n viewIdsAreUnique,\n} from \"./schemaRules\";\n\n// ---------------------------------------------------------------------------\n// Shared predicate shapes\n// ---------------------------------------------------------------------------\n\n/** Optional visibility predicate shared by actions and fields: the target\n * shows only when the open record's `field` (stringified) is one of `in`.\n * Domain-free — `field` is any non-empty key, `in` a non-empty array of\n * non-empty values; the host never interprets the meaning.\n *\n * `trim().min(1)` rather than bare `min(1)` so a whitespace-only string\n * (\" \") fails validation — otherwise the cell formatter / dropdown would\n * render visual blanks that look like missing data. Applied consistently to\n * every \"non-empty string\" slot in this file (CodeRabbit PR #1497). */\nexport const WhenZ = z.object({\n field: z.string().trim().min(1),\n in: z.array(z.string().trim().min(1)).min(1),\n});\n\n// `where` — a richer AND-of-conditions predicate than the single-field\n// `WhenZ` above: typed comparison ops (`eq/ne/in/contains/gt/gte/lt/lte`)\n// evaluated by `./where`'s `matchesWhere`. Consumed by `flag` fields\n// (below) and `dynamicIcon` (further down). Defined here, ahead of the\n// field specs, so `FlagFieldZ` can reference it.\n//\n// A condition's comparison value is either a literal `value` or a\n// `valueFrom` reference to another record's field (e.g. a `_config`\n// singleton's `defaultCity`, resolved at compute time against the source\n// collection's own records — see the server's `dynamicIcon.ts`\n// `recordsById`). Exactly one of the two is required: neither (nothing to\n// compare against) and both (ambiguous which wins) are equally meaningless.\n// `record` omitted → the SAME record being matched (field-to-field compare,\n// e.g. `spent > budget`); set → another record by primaryKey (e.g. `_config`).\nexport const ValueRefZ = z.object({\n record: z.string().trim().min(1).optional(),\n field: z.string().trim().min(1),\n});\nexport const WhereCondZ = z\n .object({\n field: z.string().trim().min(1),\n op: z.enum([\"eq\", \"ne\", \"in\", \"gt\", \"gte\", \"lt\", \"lte\", \"contains\"]),\n value: z.union([z.string(), z.array(z.string())]).optional(),\n valueFrom: ValueRefZ.optional(),\n })\n .refine((cond) => (cond.value !== undefined) !== (cond.valueFrom !== undefined), {\n message: \"a where condition must declare exactly one of `value` (a literal) or `valueFrom` (a reference to another record's field), never both or neither\",\n path: [\"value\"],\n })\n .refine((cond) => cond.value === undefined || (cond.op === \"in\") === Array.isArray(cond.value), {\n message: \"`in` requires an array `value` (the allowed set); every other op requires a single string `value`\",\n path: [\"value\"],\n });\nexport const WhereZ = z.array(WhereCondZ);\n\n// ---------------------------------------------------------------------------\n// Field specs — a discriminated union on `type`\n// ---------------------------------------------------------------------------\n\n// Keys every field variant carries. `when` gates visibility (list cell,\n// edit form, detail view — purely presentational, a hidden field's stored\n// value is never cleared; only honoured on top-level fields). `primary`\n// marks the field whose value is the record's filename (exactly one per\n// schema — enforced by `acceptParsedSchema`, not here). The referenced\n// `when.field` is validated to be a real top-level field by a schema-level\n// refine below (a field can't see its siblings here).\nconst fieldBase = {\n label: z.string().min(1),\n primary: z.boolean().optional(),\n required: z.boolean().optional(),\n when: WhenZ.optional(),\n};\n\n// A field that renders as money must declare where its currency comes from —\n// otherwise the formatter silently falls back to USD and mislabels non-USD\n// amounts. Two ways to satisfy it: a literal `currency` (an ISO 4217 code,\n// fixed for every record) or a `currencyField` naming a sibling record field\n// that holds the code (per-record, e.g. an invoice's `currency` enum; resolved\n// against the TOP-LEVEL record even for money sub-fields inside a table). At\n// least one is required. The stored value is always a plain decimal number;\n// currency is presentation only.\nconst currencyKeys = {\n currency: z.string().trim().min(1).optional(),\n currencyField: z.string().trim().min(1).optional(),\n};\nconst hasCurrencySource = (spec: { currency?: string | undefined; currencyField?: string | undefined }): boolean =>\n spec.currency !== undefined || spec.currencyField !== undefined;\nconst currencyMessage = {\n message:\n \"fields that render as money (type 'money', or 'derived' with display 'money') must declare either a literal `currency` (ISO 4217 code, e.g. 'USD', 'JPY') or a `currencyField` naming the record field that holds the code\",\n path: [\"currency\"],\n};\n\nconst slugMessage = (key: string) => ({\n message: `\\`${key}\\` must be a valid collection slug (alphanumeric / hyphen / underscore, no path separators)`,\n path: [key],\n});\n\n/** The plain scalar field types. Stored and edited as primitive values; no\n * variant-specific keys.\n * - `image`: a workspace-relative image path (e.g. a `data/attachments/...`\n * upload); rendered as an <img> in the detail view (not the list table —\n * a per-row fetch is too expensive at scale). Stored as a plain string.\n * - `file`: a workspace-relative file path as a plain string (e.g. an\n * `artifacts/html/<name>.html` app). Rendered as a clickable link in both\n * the list table and the detail view: HTML / SVG artifacts open their\n * rendered form in a new tab; any other path opens in the File Explorer. */\nconst ScalarFieldZ = z.object({\n type: z.enum([\"string\", \"text\", \"email\", \"number\", \"date\", \"datetime\", \"boolean\", \"markdown\", \"image\", \"file\"]),\n ...fieldBase,\n});\n\n/** A link to another collection: the record stores the target item's\n * primary-key slug and the host renders a clickable link + dropdown picker.\n * `to` must be a real slug (not `../foo`, not `mc-clients/extra` — see\n * Codex P2 on PR #1495); whether the target collection exists resolves\n * fail-soft at render time, never here. */\nconst RefFieldZ = z\n .object({\n type: z.literal(\"ref\"),\n ...fieldBase,\n to: z.string().min(1),\n })\n .refine((spec) => isSafeSlug(spec.to), slugMessage(\"to\"));\n\n/** A money amount. See `currencyKeys` for the currency-source contract. */\nconst MoneyFieldZ = z\n .object({\n type: z.literal(\"money\"),\n ...fieldBase,\n ...currencyKeys,\n })\n .refine(hasCurrencySource, currencyMessage);\n\n/** A closed set of allowed string values. The form renders a `<select>`\n * populated from `values`; storage is a plain string. */\nconst EnumFieldZ = z.object({\n type: z.literal(\"enum\"),\n ...fieldBase,\n values: z.array(z.string().trim().min(1)).min(1),\n});\n\n// Sub-fields inside a `table.of` map: the regular field types minus `table`\n// (no nested tables) and `derived` (no computed columns inside a table —\n// would need the evaluator to walk the row context, defer until a real need\n// surfaces). Also no `when` / `primary` — rows have neither visibility\n// gating nor filenames.\nconst subFieldBase = {\n label: z.string().min(1),\n required: z.boolean().optional(),\n};\nconst SubScalarFieldZ = z.object({\n type: z.enum([\"string\", \"text\", \"email\", \"number\", \"date\", \"datetime\", \"boolean\", \"markdown\"]),\n ...subFieldBase,\n});\nconst SubRefFieldZ = z.object({ type: z.literal(\"ref\"), ...subFieldBase, to: z.string().min(1) }).refine((spec) => isSafeSlug(spec.to), slugMessage(\"to\"));\nconst SubMoneyFieldZ = z.object({ type: z.literal(\"money\"), ...subFieldBase, ...currencyKeys }).refine(hasCurrencySource, currencyMessage);\nconst SubEnumFieldZ = z.object({ type: z.literal(\"enum\"), ...subFieldBase, values: z.array(z.string().trim().min(1)).min(1) });\n\nexport const SubFieldSpecZ = z.discriminatedUnion(\"type\", [SubScalarFieldZ, SubRefFieldZ, SubMoneyFieldZ, SubEnumFieldZ]);\n\n/** A flat sub-table: each row is a record of `of`'s sub-schema (insertion\n * order = column order). v0 disallows nested tables and derived columns to\n * keep the editor + evaluator simple. */\nconst TableFieldZ = z\n .object({\n type: z.literal(\"table\"),\n ...fieldBase,\n of: z.record(z.string(), SubFieldSpecZ),\n })\n .refine((spec) => Object.keys(spec.of).length > 0, {\n message: \"fields with type 'table' must declare a non-empty `of` (sub-schema for each row)\",\n path: [\"of\"],\n });\n\n/** A computed scalar: `formula` is a tiny expression evaluated against the\n * record — `+ - * /`, parens, identifier refs to top-level fields,\n * `sum(tableField[].col)`, and `sum(tableField[].col * tableField[].col)`\n * (see `./derivedFormula`). `display` picks the inner type the value renders\n * as (default `\"number\"`) — restricted to the non-composite display targets,\n * since a derived value is a scalar. Never stored; computed by `deriveAll`\n * on both server and client. */\nconst DerivedFieldZ = z\n .object({\n type: z.literal(\"derived\"),\n ...fieldBase,\n formula: z.string().trim().min(1),\n display: z.enum([\"string\", \"number\", \"money\", \"date\"]).optional(),\n ...currencyKeys,\n })\n .refine((spec) => spec.display !== \"money\" || hasCurrencySource(spec), currencyMessage);\n\n/** Pulls a record from another collection into the read-only detail view.\n * Display-only — nothing is stored on this record, so it never appears in\n * the list table or the edit form. Must declare a valid `to` slug (same\n * path-traversal guard as `ref`) AND exactly one of `id` (a fixed target\n * record, e.g. `me` for the singleton profile — same for every record) or\n * `idField` (a sibling top-level field naming the per-record target, e.g.\n * an invoice's `issuerId` selecting which profile to embed as the bill-from\n * block; an absent/empty value resolves fail-soft to \"no record\"). The\n * `idField` target is validated to be a real `ref`/`string` field by a\n * schema-level refine below. */\nconst EmbedFieldZ = z\n .object({\n type: z.literal(\"embed\"),\n ...fieldBase,\n to: z.string().min(1),\n id: z.string().trim().min(1).optional(),\n idField: z.string().trim().min(1).optional(),\n })\n .refine((spec) => isSafeSlug(spec.to) && (spec.id !== undefined) !== (spec.idField !== undefined), {\n message:\n \"fields with type 'embed' must declare a `to` (valid collection slug) and exactly one of `id` (a fixed record's primary key) or `idField` (a sibling field naming the per-record target)\",\n path: [\"id\"],\n });\n\n/** Display-only REVERSE refs (plan step ② of plans/done/collection-ontology.md):\n * a read-only sub-table of the records in collection `from` whose `via`\n * ref field stores THIS record's primary key. Stores nothing (joins\n * `COMPUTED_TYPES`); resolution is shared server/client via\n * `core/backlinks.ts`. `display` names the `from` columns to show;\n * `filter` (the standard `when` shape, matched against each SOURCE\n * record) narrows the rows. Validation is shape-only, like `embed`:\n * `from` must be a safe slug, but whether it exists — and whether `via` /\n * `display` name real fields there — resolves fail-soft at render\n * (empty sub-table). Do NOT add cross-schema existence checks here. */\nconst BacklinksFieldZ = z\n .object({\n type: z.literal(\"backlinks\"),\n ...fieldBase,\n from: z.string().min(1),\n via: z.string().trim().min(1),\n display: z.array(z.string().trim().min(1)).min(1),\n filter: WhenZ.optional(),\n })\n .refine((spec) => isSafeSlug(spec.from), slugMessage(\"from\"));\n\n/** A cross-collection AGGREGATE over a backlink relation (plan step ⑤ of\n * plans/done/collection-ontology.md): a computed number — never stored — that\n * sums a source column (or counts rows) over the records in `from` whose\n * `via` ref points at this record. Same `from`/`via`/`filter` vocabulary\n * and reverse-loading machinery as `backlinks`; resolution shared\n * server/client via `core/backlinks.ts`'s `rollupValue`. Deliberately a\n * STRUCTURED field, not `sumOver(...)` formula syntax — the derived\n * evaluator's no-string-literals boundary stays untouched — and\n * deliberately just `sum` | `count`. Rollups resolve BEFORE the formula\n * pass, so a sibling `derived` formula may read them as identifiers\n * (`played = homePlayed + awayPlayed`). Fail-soft: an unresolvable\n * `from` renders em-dash; an empty match set is a real 0. */\nconst RollupFieldZ = z\n .object({\n type: z.literal(\"rollup\"),\n ...fieldBase,\n from: z.string().min(1),\n via: z.string().trim().min(1),\n op: z.enum([\"sum\", \"count\"]),\n column: z.string().trim().min(1).optional(),\n filter: WhenZ.optional(),\n })\n .refine((spec) => isSafeSlug(spec.from), slugMessage(\"from\"))\n .refine((spec) => (spec.op === \"sum\") === (spec.column !== undefined), {\n message: 'a rollup\\'s `column` names the source column to aggregate: required for op \"sum\", meaningless for op \"count\"',\n path: [\"column\"],\n });\n\n/** A checkbox that is a pure PROJECTION of an `enum` field — it stores\n * nothing of its own. Checked when the enum named by `field` equals\n * `onValue`; toggling writes `onValue` / `offValue` back to that enum\n * field. Lets a \"done\" checkbox front a kanban `status` field with the enum\n * as the single source of truth (no separate stored boolean to keep in\n * sync). `field` / `onValue` / `offValue` are validated against the target\n * enum's `values` by a schema-level refine below. */\nconst ToggleFieldZ = z.object({\n type: z.literal(\"toggle\"),\n ...fieldBase,\n field: z.string().trim().min(1),\n onValue: z.string().trim().min(1),\n offValue: z.string().trim().min(1),\n});\n\n/** A computed boolean: the record matched against a `where` predicate\n * (the same AND-of-conditions shape `dynamicIcon` uses; evaluated by\n * `./where`'s `matchesWhere`). Generic state summaries — isDone,\n * isPassed, isQualified — become declared fields, so every\n * field-driven mechanism (table cells, detail view, completion via\n * `completionField`) picks them up without special cases. Never\n * stored; computed by `deriveAll` on both server and client in the\n * same saturation loop as `derived`, so a flag may read derived /\n * rollup values — and other flags (`matchesWhere` stringifies, so\n * `eq \"true\"` composes). Deliberately a STRUCTURED field, not formula\n * syntax — the derived evaluator's no-string-literals boundary stays\n * untouched (same reasoning as `rollup`). Cross-record\n * `valueFrom.record` is rejected here: per-record evaluation has no\n * `recordsById`, so it could only ever silently never-match.\n * `where` condition fields are validated to exist by a schema-level\n * refine below (a field can't see its siblings). */\nconst FlagFieldZ = z\n .object({\n type: z.literal(\"flag\"),\n ...fieldBase,\n where: WhereZ.min(1),\n })\n .refine((spec) => spec.where.every((cond) => cond.valueFrom?.record === undefined), {\n message:\n \"a flag's `where` cannot use `valueFrom.record` (cross-record references are unresolvable in per-record evaluation); use a literal `value` or a same-record `valueFrom` (field-to-field)\",\n path: [\"where\"],\n });\n\nexport const FieldSpecZ = z.discriminatedUnion(\"type\", [\n ScalarFieldZ,\n RefFieldZ,\n MoneyFieldZ,\n EnumFieldZ,\n TableFieldZ,\n DerivedFieldZ,\n EmbedFieldZ,\n BacklinksFieldZ,\n RollupFieldZ,\n ToggleFieldZ,\n FlagFieldZ,\n]);\n\n// ---------------------------------------------------------------------------\n// Actions & custom views\n// ---------------------------------------------------------------------------\n\n// Keys every action variant carries.\nconst actionBase = {\n id: z.string().trim().min(1),\n label: z.string().trim().min(1),\n icon: z.string().trim().min(1).optional(),\n};\n\n/** The LLM-seeded action kinds — same shape, different visibility:\n * - `\"chat\"` — start a new VISIBLE chat in `role` with the templated\n * seed prompt (judgment work: drafting, planning, conversation).\n * - `\"agent\"` — dispatch a HIDDEN worker (origin `system`) with the SAME\n * seed; it edits records via manageCollection and finishes silently\n * (mechanical enrichment: refresh a price, fetch metadata). Spinner\n * while running, deduped failure bell on error — see\n * server/api/routes/collectionAgentActions.ts. */\nconst SeededActionZ = z.object({\n kind: z.enum([\"chat\", \"agent\"]),\n ...actionBase,\n role: z.string().trim().min(1),\n template: z\n .string()\n .trim()\n .min(1)\n .refine(isSafeActionTemplatePath, \"must be a safe path under `templates/` (e.g. `templates/invoice.md`; no `..`, no leading `/`, no backslash)\"),\n when: WhenZ.optional(),\n});\n\n/** `kind: \"mutate\"` — a declarative, HOST-executed write; no LLM, no\n * tokens (plan step ④ of plans/done/collection-ontology.md). Clicking the\n * button (after an optional `params` mini-form) merges `set` into the\n * record: values are literals or `$params.<name>` references. `require`\n * is the state gate — the standard `when` shape, both the visibility\n * rule AND the server-side authorization rule, exactly like `when` on\n * the seeded kinds. `params` reuses the table sub-field DSL, and the\n * form is validated by the SAME compiled record checks `putItems` uses\n * (`recordFieldProblem`), not a third mechanism. Record-level only —\n * a collection-level mutate has no record to write (schema refine\n * below). Merge semantics make half-states unconstructible THROUGH\n * THIS PATH; the raw file stays editable by design (lint, not lock). */\nconst MutateActionZ = z\n .object({\n kind: z.literal(\"mutate\"),\n ...actionBase,\n require: WhenZ.optional(),\n params: z.record(z.string().trim().min(1), SubFieldSpecZ).optional(),\n set: z.record(z.string().trim().min(1), z.union([z.string(), z.number(), z.boolean()])),\n })\n .refine((spec) => Object.keys(spec.set).length > 0, {\n message: \"a mutate action's `set` must name at least one field to write\",\n path: [\"set\"],\n });\n\n/** A schema-declared record action, rendered as a button in the read-only\n * detail view. Domain-free: the host validates the shape; the meaning\n * (role + template prose, or the declarative `set`) is data. A\n * discriminated union on `kind` — each kind declares only its own keys. */\nexport const ActionSpecZ = z.discriminatedUnion(\"kind\", [SeededActionZ, MutateActionZ]);\n\n/** A custom (LLM-authored) HTML view registration. Domain-free: the host\n * validates the shape; the view's behaviour lives in the HTML file. `file`\n * is constrained to `views/*.html` (path-safe) so the view-file reader can\n * never reach the data folder or the schema/template files. `id` is\n * validated to be a real slug + unique by schema-level refines below. */\nexport const CustomViewZ = z.object({\n id: z.string().trim().min(1),\n label: z.string().trim().min(1),\n icon: z.string().trim().min(1).optional(),\n // Where the view runs. Absent ⇒ \"desktop\" (the sandboxed iframe over the\n // token/dataUrl contract), so every pre-existing view keeps its behavior.\n // \"mobile\" ⇒ served to the phone remote via getRemoteView (postMessage\n // contract, @mulmoclaude/core/remote-view) and phone-frame-previewed on\n // desktop. See plans/done/feat-remote-custom-view.md.\n target: z.enum([\"desktop\", \"mobile\"]).optional(),\n file: z\n .string()\n .trim()\n .min(1)\n .refine(isSafeCustomViewPath, \"must be a safe path under `views/` ending in `.html` (e.g. `views/year.html`; no `..`, no leading `/`, no backslash)\"),\n // A JSON translation dictionary co-located with the view (shape mirrors\n // vue-i18n locale messages; the host injects only the active locale's flat\n // string map into the iframe — see `CollectionCustomView.i18n`'s docs in\n // ./schema's source history / docs/developer.md).\n i18n: z\n .string()\n .trim()\n .min(1)\n .refine(\n isSafeCustomViewI18nPath,\n \"must be a safe path under `views/` ending in `.i18n.json` (e.g. `views/year.i18n.json`; no `..`, no leading `/`, no backslash)\",\n )\n .optional(),\n // What the view may do with the data endpoint. Defaults to [\"read\"] (least\n // privilege); the mint endpoint clamps any requested caps to this. There is\n // deliberately no \"delete\" — a view can never do more than the agent's own\n // manageCollection tool.\n capabilities: z.array(z.enum([\"read\", \"write\"])).optional(),\n // Mobile-only write policy (plans/done/feat-remote-writable-view.md). Default-deny:\n // a `target: \"mobile\"` view may patch ONLY these fields via\n // `__MC_VIEW.updateItem`, and may delete only when `allowDelete` is true. The\n // host re-derives + enforces both on every mutate — never trusting the client.\n // Ignored for desktop views (they use the token-scoped `capabilities` above).\n editableFields: z.array(z.string().trim().min(1)).optional(),\n allowDelete: z.boolean().optional(),\n // Mobile-only image inlining (plans/done/feat-remote-view-images.md). A\n // `target: \"mobile\"` view can't reach the host's localhost, so an `image`-type\n // field's workspace path is unrenderable on the phone; listing it here makes\n // the host inline it as a downscaled `data:` URL thumbnail in getItems pages.\n // Opt-in (absent ⇒ none), projection- and budget-bounded host-side. Ignored\n // for desktop views (they resolve via /api/files/raw).\n imageFields: z.array(z.string().trim().min(1)).optional(),\n imageMaxEdge: z.number().int().min(1).optional(),\n});\n\n// ---------------------------------------------------------------------------\n// Spawn (host-driven recurrence)\n// ---------------------------------------------------------------------------\n\n/** Recurrence advance for `spawn.every`. `interval` is a positive integer\n * count of `unit`s (`interval: 3` + `unit: \"month\"` = quarterly);\n * `dayOfMonth` (month/year only) is the CANONICAL day-of-month anchor\n * (1-31, read from the rule and clamped per-month at compute time so \"31st\n * of every month\" never drifts) or the `\"last\"` sentinel for end-of-month.\n * `.strict()` so the union below cleanly rejects an object carrying BOTH\n * `unit` and `fromField` (it fails this arm on the unknown `fromField`). */\nexport const EveryLiteralZ = z\n .object({\n unit: z.enum([\"day\", \"week\", \"month\", \"year\"]),\n interval: z.number().int().min(1),\n dayOfMonth: z.union([z.number().int().min(1).max(31), z.literal(\"last\")]).optional(),\n })\n .strict();\n\n/** Field-driven recurrence: pick the interval per-record by an `enum`\n * field's value — one collection can mix daily / weekly / monthly\n * obligations in a single list. `map` keys are validated to exactly cover\n * that field's `values` by a `CollectionSchemaZ` refine (which can see the\n * sibling `fields`); here each map value just has to be a well-formed\n * literal `every`. `.strict()` mirrors the literal arm so a both-keys\n * object fails this arm too. */\nexport const EveryFieldDrivenZ = z\n .object({\n fromField: z.string().trim().min(1),\n map: z.record(z.string(), EveryLiteralZ),\n })\n .strict();\n\n/** Either a single literal interval (applied to every record) or the\n * field-driven map. Two `.strict()` arms mean \"both keys\" and \"neither\n * key\" both fail validation, with no extra refine. */\nexport const EveryZ = z.union([EveryLiteralZ, EveryFieldDrivenZ]);\n\n/** Host-driven recurrence: when a record satisfies `when` (default:\n * \"`completionField` value ∈ `completionDoneValues`\"), the host creates the\n * next record with a forward-advanced `triggerField` date. `carry` copies\n * record fields verbatim onto the successor; `set` forces fixed values\n * (typically resetting the status field to its pending value). The\n * successor's id and contents are a pure function of (source record, this\n * rule); creation is create-if-absent, so the mechanism stays convergent. */\nexport const SpawnZ = z.object({\n when: WhenZ.optional(),\n every: EveryZ,\n carry: z.array(z.string().trim().min(1)).optional(),\n set: z.record(z.string(), z.unknown()).optional(),\n});\n\n// ---------------------------------------------------------------------------\n// Ingest (scheduled retrieval)\n// ---------------------------------------------------------------------------\n\n/** Declarative retrieval config for a Feed (a collection that refills itself\n * from the internet). `http-json` needs `itemsAt` (a path to the items\n * array) only when the response body isn't itself the array; rss/atom yield\n * items natively and ignore it — so no kind-specific requirement here. */\nexport const DeclarativeIngestZ = z.object({\n kind: z.enum(INGEST_KINDS),\n url: z.url(),\n schedule: z.enum(FEED_SCHEDULES),\n // Optional UTC hour (0–23) to anchor a `daily` schedule; ignored otherwise.\n atHour: z.number().int().min(0).max(23).optional(),\n itemsAt: z.string().trim().min(1).optional(),\n map: z.record(z.string().trim().min(1), z.string().trim().min(1)),\n idFrom: z.string().trim().min(1).optional(),\n maxItems: z.number().int().min(0).optional(),\n});\n\n/** Agent-performed retrieval. Valid on any collection (the primary consumer\n * is skill-backed collections — feeds keep their declarative kinds). No\n * `url`/`map`: the worker owns retrieval and record shape, seeded by\n * `template` + a summary of every record, run in `role`. `template` is\n * validated the SAME way an action's template is (safe path under\n * `templates/`), so the skill-bridge mirrors it identically. */\nexport const AgentIngestZ = z.object({\n kind: z.literal(AGENT_INGEST_KIND),\n schedule: z.enum(FEED_SCHEDULES),\n // Optional UTC hour (0–23) to anchor a `daily` schedule; ignored otherwise.\n atHour: z.number().int().min(0).max(23).optional(),\n role: z.string().trim().min(1),\n template: z\n .string()\n .trim()\n .min(1)\n .refine(isSafeActionTemplatePath, \"must be a safe path under `templates/` (e.g. `templates/refresh.md`; no `..`, no leading `/`, no backslash)\"),\n});\n\n/** The Google event fields a collection may pull from. `id` is absent on\n * purpose — it always lands in the primary field, since upsert-by-event-id\n * is what makes the sync idempotent. */\nexport const GOOGLE_CALENDAR_SOURCE_FIELDS = [\"summary\", \"start\", \"end\", \"htmlLink\", \"colorId\", \"status\", \"description\", \"location\"] as const;\n\n/** Marks a collection as the destination of the LLM-free Google Calendar\n * sync (#2095). `map` is collectionField → Google event field, so the user's\n * collection keeps whatever field names it already uses. */\nexport const GoogleCalendarSyncZ = z.object({\n /** Calendar to pull from; defaults to the user's primary. */\n calendarId: z.string().trim().min(1).optional(),\n // An empty map is silently useless rather than harmless: the sync would run\n // and write a record per event carrying ONLY the event id, so the user gets\n // rows with no content. Fail at load instead.\n map: z.record(z.string().trim().min(1), z.enum(GOOGLE_CALENDAR_SOURCE_FIELDS)).refine((map) => Object.keys(map).length > 0, {\n message: \"map at least one field — a `googleCalendar` sync with an empty map writes records that carry only the event id\",\n }),\n /** Run the push on the sync schedule too, immediately before each pull, so\n * local edits reach Google without anyone pressing a button (#2620).\n *\n * Opt-in and absent by default: a push writes to a calendar other people may\n * read, so turning it on is the user's decision, not a default. */\n autoPush: z.boolean().optional(),\n});\n\n/** `ingest` is a discriminated union on `kind`: the three declarative\n * retrievers fetch-and-map; `agent` dispatches a hidden worker. Optional on\n * every schema — skill-backed collections usually omit it; only feeds\n * discovered from `<workspace>/feeds/` are REQUIRED to carry it (gated by\n * `acceptParsedSchema`). */\nexport const IngestZ = z.discriminatedUnion(\"kind\", [DeclarativeIngestZ, AgentIngestZ]);\n\n// ---------------------------------------------------------------------------\n// dynamicIcon (data-driven launcher icon) and its `where` predicate\n// ---------------------------------------------------------------------------\n\n// Data-driven launcher-icon override (see `CollectionSchema.dynamicIcon`).\n// `source.collection` may name ANY collection (self or cross-collection),\n// so — unlike `ref`/`embed`/`when.field` elsewhere in this file — its\n// shape is validated here without a cross-field refine against a specific\n// target schema (that collection may not even be loaded yet); a bad\n// `source.collection`/`orderBy`/condition `field` fails soft at compute\n// time instead (`computeCollectionIcon`), matching this feature's locked\n// design (see plans/done/feat-dynamic-collection-icons.md \"Open questions\").\n//\n// `where` is the AND-of-conditions predicate defined at the top of this\n// file (`WhereZ`, shared with `flag` fields) — see `./where` for the\n// evaluator.\nexport const DynamicIconSourceZ = z.object({\n collection: z.string().trim().min(1),\n from: z.enum([\"latest\", \"first\", \"when\"]).optional(),\n orderBy: z.string().trim().min(1).optional(),\n where: WhereZ.optional(),\n});\nexport const DynamicIconRuleZ = z.object({\n where: WhereZ,\n icon: z.string().trim().min(1),\n});\nexport const DynamicIconSpecZ = z.object({\n source: DynamicIconSourceZ,\n rules: z.array(DynamicIconRuleZ),\n fallback: z.string().trim().min(1).optional(),\n});\n\n// ---------------------------------------------------------------------------\n// dataSource (external read-only data file)\n// ---------------------------------------------------------------------------\n\n/** External-data collection: the records ARE the rows of a user-supplied\n * data file (v1: CSV), queried through DuckDB — never copied into\n * `<dataDir>/<id>.json` files. Declaring `dataSource` makes the collection\n * **read-only** in every UI/tool write path; updates happen by replacing /\n * editing the file itself (file-watch republishes the views). `path` is\n * workspace-relative and containment-checked exactly like `dataPath`. The\n * row-id column is the schema's existing `primaryKey` — there is\n * deliberately no second key concept here.\n * See plans/done/feat-collection-csv-duckdb-source.md. */\nexport const DataSourceZ = z.object({\n type: z.literal(\"csv\"),\n path: z.string().min(1),\n});\n\n// ---------------------------------------------------------------------------\n// storage (alternative writable record backend)\n// ---------------------------------------------------------------------------\n\n/** Alternative WRITABLE storage backend for a collection's records —\n * unlike `dataSource` (external read-only file), a `storage` collection\n * behaves like a normal writable collection; only where the rows live\n * changes. The store factory registry (`server/store.ts`) picks the\n * implementation by `type` (plans/done/refactor-storage-virtualization.md).\n *\n * A discriminated union rather than one shape with optional keys, because\n * only the sqlite variant is a workspace FILE: its `path` is\n * workspace-relative and containment-checked exactly like `dataPath`, while\n * the firestore variant has no path to check — its records are not on this\n * machine at all. Optional keys would let each arm accept the other's, and\n * the compiler would stop being the thing that tells you which. */\nexport const StorageZ = z.discriminatedUnion(\"type\", [\n /** Records in a single SQLite database file (`node:sqlite`, one JSON\n * record per row keyed by the primaryKey). */\n z.object({\n type: z.literal(\"sqlite\"),\n path: z.string().min(1),\n }),\n /** Records as Firestore documents of a SHARED collection, at\n * `apps/{aid}/collections/{cid}/items/{id}`.\n *\n * This variant declares NO location, and that is the whole shape of it:\n * `aid` comes from the repository's `app.json` (it is one per app, not one\n * per collection — four collections share one member roster), and `cid` is\n * always this collection's slug. There is nothing left for a schema to say.\n *\n * `.strict()` (the same reason the `spawn` arms use it): stripping an\n * unknown key silently would confirm a wrong mental model — an author who\n * writes `path` believes their records land there, and an author who writes\n * `cid` believes the collection can be named something other than its slug.\n * Only this new arm is strict; the sqlite arm stays permissive so an\n * existing schema carrying a stray key doesn't start failing. */\n z\n .object({\n type: z.literal(\"firestore\"),\n })\n .strict(),\n]);\n\n// ---------------------------------------------------------------------------\n// The whole schema\n// ---------------------------------------------------------------------------\n\n/** The schema's SHAPE, before any cross-field rule runs. Named so the rules in\n * `./schemaRules` can type their argument against it — they receive whatever\n * this parses to, and nothing narrower. */\nconst CollectionObjectZ = z.object({\n title: z.string().min(1),\n icon: z.string().min(1),\n // Exactly one of `dataPath` (native JSON-file records), `dataSource`\n // (external read-only data file), or `storage` (alternative writable\n // backend) — enforced by a refine below.\n dataPath: z.string().min(1).optional(),\n dataSource: DataSourceZ.optional(),\n storage: StorageZ.optional(),\n primaryKey: z.string().min(1),\n // When set, the collection holds at most one record whose primary\n // key is this exact value (e.g. `me` for the business profile).\n // The host fixes the create form's primary key to it and hides the\n // Add button once the record exists.\n singleton: z.string().trim().min(1).optional(),\n fields: z.record(z.string(), FieldSpecZ),\n actions: z.array(ActionSpecZ).optional(),\n // Collection-level actions (header buttons). Same shape as `actions`;\n // the `when` predicate is ignored (no record context). The seed\n // prompt injects a progress summary of all records instead.\n collectionActions: z.array(ActionSpecZ).optional(),\n // Completion-tracking pair: when both are set, item-create fires a\n // notification that clears once `completionField` transitions into\n // `completionDoneValues`. The two are bound together — declaring\n // one without the other is a misconfiguration the cross-field\n // refine below rejects.\n completionField: z.string().trim().min(1).optional(),\n completionDoneValues: z.array(z.string().trim().min(1)).min(1).optional(),\n // Optional human-readable label for the completion notification's\n // title — names the field whose value reads better than the opaque\n // primaryKey (e.g. a `name` field). Falls back to the primaryKey\n // value at render time when unset or empty.\n displayField: z.string().trim().min(1).optional(),\n // Time gate: names a `date` field that delays the completion bell\n // until the clock reaches it. Requires the completion pair (the bell\n // still clears via the done value). Validated to name a real `date`\n // field by refines below.\n triggerField: z.string().trim().min(1).optional(),\n // Lead time in whole days — fire the bell this many days before\n // `triggerField`. Non-negative; requires `triggerField` (refine below).\n triggerLeadDays: z.number().int().min(0).optional(),\n // Host-driven recurrence; requires `triggerField`. See SpawnZ.\n spawn: SpawnZ.optional(),\n // Calendar view anchor: names a `date` field whose value places each\n // record on a month grid. Validated to name a real `date` field by a\n // refine below. Optional — the toggle auto-derives from any `date`\n // field when this is unset.\n calendarField: z.string().trim().min(1).optional(),\n // Multi-day span end: a second `date` field the calendar record spans\n // to. Requires `calendarField`; validated to name a real `date` field.\n calendarEndField: z.string().trim().min(1).optional(),\n // Day (time-allocation) view time source: names a string field holding a\n // free-form time or time-range (e.g. \"14:00-17:00\", \"17:00-\", \"16:30\").\n // Consulted only when the date fields are date-only. Requires\n // `calendarField`; validated to name a real field by a refine below.\n calendarTimeField: z.string().trim().min(1).optional(),\n // Kanban board group: names an `enum` field whose value buckets each\n // record into a column. Validated to name a real `enum` field by a\n // refine below. Optional — the toggle auto-derives from any `enum`\n // field when this is unset.\n kanbanField: z.string().trim().min(1).optional(),\n // Custom (LLM-authored) HTML views. Each renders in a sandboxed iframe\n // over the records. Optional, so every existing schema validates\n // unchanged. Ids validated to be valid + unique slugs by refines below.\n views: z.array(CustomViewZ).optional(),\n // Completion-bell gate: only notify for records matching this predicate\n // (e.g. high-priority todos). Reuses the `when` shape; requires\n // `completionField`; field validated to exist by refines below.\n notifyWhen: WhenZ.optional(),\n // Declarative retrieval config. Present only on Feeds (collections in\n // the `<workspace>/feeds/` registry). Optional, so every existing\n // skill schema validates unchanged.\n ingest: IngestZ.optional(),\n // Declares this collection as the destination of the LLM-free Google\n // Calendar sync. Optional, so every existing schema validates unchanged.\n googleCalendar: GoogleCalendarSyncZ.optional(),\n // Data-driven launcher-icon override. Optional, so every existing\n // schema validates unchanged; `source` is required within it.\n dynamicIcon: DynamicIconSpecZ.optional(),\n});\n\nexport type CollectionSchemaInput = z.infer<typeof CollectionObjectZ>;\n\nconst BareCollectionSchemaZ = CollectionObjectZ\n // Exactly one storage declaration: native records need `dataPath`, an\n // external data file needs `dataSource`, an alternative backend needs\n // `storage`. Zero (nowhere to read) and several (ambiguous which wins)\n // are equally meaningless — fail loudly at load instead of picking\n // silently.\n .refine(declaresExactlyOneStore, {\n message:\n \"declare exactly one of `dataPath` (native JSON records), `dataSource` (external read-only data file), or `storage` (alternative writable backend)\",\n path: [\"dataPath\"],\n })\n // NOTE: `storage` collections support the full write machinery\n // (`spawn` / `completionField` / `triggerField` / `singleton` / `ingest`\n // / mutate actions) — spawn and the watcher reconcilers go through the\n // CollectionStore seam. What DRIVES that reconciliation differs by\n // backend: `sqlite` has a db-file watcher, while `firestore` has no file\n // to watch and is reconciled on the clock tick instead\n // (`tickUnwatchedCollections`) until the snapshot listener lands. Either\n // way the declared behaviour runs — that equivalence is what lets this\n // refine stay backend-agnostic.\n // A `dataSource` collection is read-only by definition, so schema-level\n // write machinery can never fire: `singleton` pins CREATES, `ingest`\n // REFILLS records, `spawn` WRITES successor records. Rejecting them at\n // validation kills whole classes of writes before any runtime guard.\n .refine(dataSourceDeclaresNoWriteMachinery, {\n message: \"a `dataSource` collection is read-only — it cannot declare `singleton`, `ingest`, `spawn`, or `googleCalendar` (all of them write records)\",\n path: [\"dataSource\"],\n })\n // The sync writes each mapped value into a declared field, and puts the\n // Google event id in the primary field — so a map key that names no field\n // (or names the primary) would silently drop data or fight the id.\n .refine(googleCalendarMapNamesStoredFields, {\n message: \"a `googleCalendar` map key must name a declared, non-computed field, and never the primaryKey (that always holds the Google event id)\",\n path: [\"googleCalendar\"],\n })\n // Same rule for declarative host writes: a mutate action writes the\n // record it's invoked on.\n .refine(dataSourceDeclaresNoMutateAction, {\n message: 'a `dataSource` collection is read-only — its actions cannot use `kind: \"mutate\"` (a host write); use `chat`/`agent` actions instead',\n path: [\"dataSource\"],\n })\n // The singleton value becomes a record id (and thus a `<id>.json`\n // filename), so it must satisfy the SAME record-id rule the write path\n // enforces — otherwise the create form would lock the primary key to a\n // value the POST route then rejects as an invalid item id, making the\n // collection impossible to initialize (Codex P1).\n .refine(singletonIsAValidRecordId, {\n message: \"schema `singleton` must be a valid item id (alphanumeric / hyphen / underscore / interior dot, no `..` or path separators)\",\n path: [\"singleton\"],\n })\n // Action ids must be unique so the dispatch route resolves\n // unambiguously.\n .refine(actionIdsAreUnique, {\n message: \"schema `actions` must have unique `id`s\",\n path: [\"actions\"],\n })\n // Collection-level action ids must likewise be unique.\n .refine(collectionActionIdsAreUnique, {\n message: \"schema `collectionActions` must have unique `id`s\",\n path: [\"collectionActions\"],\n })\n // A mutate action's `set` writes real STORED fields: a typo'd key\n // would write a stray value forever, a computed/projected field is\n // never persisted, and the primaryKey is the filename (renaming is\n // not a mutation).\n .refine(mutateSetKeysNameStoredFields, {\n message: \"a mutate action's `set` keys must name declared, non-computed fields (and never the primaryKey)\",\n path: [\"actions\"],\n })\n // Every `$params.<name>` reference in `set` must name a declared\n // param — an undeclared one would silently no-op the assignment.\n .refine(mutateParamRefsAreDeclared, {\n message: \"a mutate action's `$params.<name>` references must name keys declared in its `params`\",\n path: [\"actions\"],\n })\n // A collection-level action has no record to write.\n .refine(collectionActionsAreNotMutate, {\n message: '`collectionActions` cannot contain `kind: \"mutate\"` — a collection-level action has no record to write',\n path: [\"collectionActions\"],\n })\n // A `currencyField` pointer must name a real top-level field that\n // holds a code string — a typo (`curreny`) would otherwise pass the\n // per-field check, then silently fall back to the literal / USD at\n // render and mislabel amounts. Checked at the schema level because a\n // field can't see its siblings.\n .refine(currencyFieldRefsNameCodeFields, {\n message: \"a money field's `currencyField` must name a top-level `string`, `text`, or `enum` field that holds the currency code\",\n path: [\"fields\"],\n })\n // Completion-tracking pair must be declared together: declaring\n // `completionField` without `completionDoneValues` (or vice-versa)\n // is meaningless — the host would either never fire (no done values\n // to compare against) or never clear (no field to read). Bound\n // together so the misconfiguration fails loudly at load time.\n // EXCEPTION: when `completionField` names a `flag` field, done ⇔ the\n // flag's `where` matches, so `completionDoneValues` carries no\n // information and MUST be omitted (declaring it would invite a\n // contradictory second source of truth).\n .refine(completionPairIsCoherent, {\n message:\n \"schema `completionField` and `completionDoneValues` must be declared together (both set, or both omitted) — unless `completionField` names a `flag` field, in which case `completionDoneValues` must be omitted (done ⇔ the flag matches)\",\n path: [\"completionField\"],\n })\n // `completionField` must name a real top-level field — a typo would\n // silently disable the notification mechanism otherwise.\n .refine(completionFieldIsDeclared, {\n message: \"schema `completionField` must name a top-level field declared in `fields`\",\n path: [\"completionField\"],\n })\n // `displayField`, like `completionField`, must name a real top-level\n // field — a typo would silently fall back to the primaryKey forever.\n .refine(displayFieldIsDeclared, {\n message: \"schema `displayField` must name a top-level field declared in `fields`\",\n path: [\"displayField\"],\n })\n // A field's `when.field` gates its visibility against a sibling's\n // value, so it must name a real top-level field — a typo would\n // silently keep the field hidden forever (the gate never matches).\n // Checked at the schema level because a field can't see its siblings.\n .refine(fieldVisibilityGatesNameDeclaredFields, {\n message: \"a field's `when.field` must name a top-level field declared in `fields`\",\n path: [\"fields\"],\n })\n // A flag's `where` reads sibling fields (both `cond.field` and a\n // same-record `valueFrom.field`), so each must name a real top-level\n // field — a typo would silently pin the flag false forever (`ne`:\n // true forever). Checked at the schema level because a field can't\n // see its siblings.\n .refine(flagConditionsNameDeclaredFields, {\n message: \"a flag field's `where` conditions must name top-level fields declared in `fields` (both `field` and a same-record `valueFrom.field`)\",\n path: [\"fields\"],\n })\n // A flag named by `completionField` is evaluated against the RAW\n // record — the reconciler (and spawn's fallback) read items straight\n // off disk, BEFORE any `deriveAll` enrichment — so its `where` may\n // only reference STORED fields. A condition over a computed sibling\n // (derived/rollup/toggle/flag/embed/backlinks) would see an absent\n // key: `ne` matches vacuously, every other op reads false, and the\n // bell would clear wrongly / never. General (non-completion) flags\n // keep the full vocabulary — the UI evaluates them post-enrichment.\n .refine(completionFlagReadsOnlyStoredFields, {\n message:\n \"a `flag` named by `completionField` may only reference STORED fields in its `where` — completion is evaluated against the raw record (before deriveAll), where computed values (derived/rollup/toggle/flag/embed/backlinks) are absent\",\n path: [\"completionField\"],\n })\n // The spawn-inert guard (`spawnSuccessorStartsInert`) statically\n // checks that a successor is not born already matching the spawn\n // predicate; it cannot see through a flag's `where` (the predicate\n // would need full record evaluation against `set`/`carry`). So a\n // schema whose completion is flag-form may only spawn with an\n // explicit `spawn.when` — which the guard CAN check.\n .refine(flagCompletionSpawnDeclaresWhen, {\n message:\n \"a schema whose `completionField` names a `flag` field must declare an explicit `spawn.when` (the spawn-inert check cannot statically evaluate a flag's `where`)\",\n path: [\"spawn\"],\n })\n // An `embed`'s `idField` resolves the target record id from a sibling's\n // value, so it must name a real top-level field — and one whose stored\n // value is a plain id string. Only `ref` / `string` qualify: the editor\n // writes the picked id into that field, so a non-persisted or composite\n // type (`embed` / `derived` / `toggle` / `table` / `number` / …) would\n // either not round-trip on save or hold no usable id. Restricting it\n // makes the misconfiguration fail at schema load, not silently at\n // render. `idField` is ignored on non-`embed` fields, so only check\n // there. Schema-level because a field can't see its siblings.\n .refine(embedIdFieldsNameIdBearingFields, {\n message: \"an embed field's `idField` must name a top-level `ref` or `string` field declared in `fields`\",\n path: [\"fields\"],\n })\n // `triggerField` requires the completion pair: the time gate only\n // suppresses the *completion* bell until the date, and the bell still\n // clears via `completionDoneValues`. Without completion there is no\n // bell to gate (or clear), so the declaration is meaningless.\n .refine(triggerFieldRequiresCompletion, {\n message: \"schema `triggerField` requires `completionField` / `completionDoneValues` (the gated bell still clears via the done value)\",\n path: [\"triggerField\"],\n })\n // `triggerField` must name a real `date` field — the gate parses its\n // value as `YYYY-MM-DD`; any other type can't be compared to the clock.\n .refine(triggerFieldIsADateField, {\n message: \"schema `triggerField` must name a top-level `date` field declared in `fields`\",\n path: [\"triggerField\"],\n })\n // `triggerLeadDays` only means something relative to a trigger date.\n .refine(triggerLeadDaysRequiresTriggerField, {\n message: \"schema `triggerLeadDays` requires `triggerField` (it shifts when that field's bell fires)\",\n path: [\"triggerLeadDays\"],\n })\n // `spawn` advances `triggerField` to compute the successor's trigger\n // date, so the schema must declare one.\n .refine(spawnRequiresTriggerField, {\n message: \"schema `spawn` requires `triggerField` (the successor's trigger date is `triggerField` advanced by `spawn.every`)\",\n path: [\"spawn\"],\n })\n // `spawn.when.field` and every `spawn.carry` entry must name real\n // top-level fields — a typo would silently never match / never copy.\n .refine(spawnWhenFieldIsDeclared, {\n message: \"schema `spawn.when.field` must name a top-level field declared in `fields`\",\n path: [\"spawn\"],\n })\n .refine(spawnCarryEntriesAreDeclared, {\n message: \"every `spawn.carry` entry must name a top-level field declared in `fields`\",\n path: [\"spawn\"],\n })\n // A successor must NOT be born already matching its own spawn predicate\n // — it would re-spawn on its first reconcile, fanning out into an\n // unbounded chain of records. The predicate field/values are `spawn.when`\n // when given, else the completion-done pair (the default predicate). The\n // successor's value for that field is `set[field]` if set, else the\n // carried source value (which matched, by definition, when the spawn\n // fired) if carried, else absent (safe). Reject the first two when they\n // land on a matching value.\n .refine(spawnSuccessorStartsInert, {\n message:\n \"`spawn` must leave the successor in a non-matching state (e.g. `set` the status to a pending value); seeding the predicate field to a matching value via `set`/`carry` would respawn forever\",\n path: [\"spawn\"],\n })\n // Field-driven `spawn.every` (§4.1): `fromField` must name a top-level\n // `enum` — the only field type with a closed, finite value set to drive\n // the `map` and the form `<select>`.\n .refine(fieldDrivenFromFieldIsEnum, {\n message: \"`spawn.every.fromField` must name a top-level `enum` field declared in `fields`\",\n path: [\"spawn\"],\n })\n // Field-driven `spawn.every` (§4.2): the `map` keys must exactly cover the\n // enum's `values` — a missing key would stall a record at that frequency;\n // an extra key signals a map left stale after an enum edit.\n .refine(fieldDrivenMapCoversValues, {\n message: \"`spawn.every.map` keys must exactly cover the `values` of the `enum` named by `fromField` (no missing or extra keys)\",\n path: [\"spawn\"],\n })\n // Field-driven `spawn.every` (§4.5): `fromField` must be carried (or `set`)\n // onto the successor, or the next spawn in the chain can't resolve an\n // interval and the recurrence silently halts.\n .refine(fieldDrivenFromFieldCarried, {\n message:\n \"`spawn.every.fromField` must appear in `spawn.carry`, or be written by `spawn.set` to a value present in `spawn.every.map`, so the successor keeps a resolvable recurrence interval\",\n path: [\"spawn\"],\n })\n // `calendarField` must name a real `date`/`datetime` field — the calendar\n // view parses its value to place records on the month grid (a `datetime`\n // anchor also carries the clock for the day view); any other type can't be\n // put on a calendar.\n .refine(calendarFieldIsDateLike, {\n message: \"schema `calendarField` must name a top-level `date` or `datetime` field declared in `fields`\",\n path: [\"calendarField\"],\n })\n // `calendarEndField` marks the end of a multi-day span, so it only means\n // something alongside a start anchor.\n .refine(calendarEndFieldRequiresCalendarField, {\n message: \"schema `calendarEndField` requires `calendarField` (it marks the end of the span that starts at `calendarField`)\",\n path: [\"calendarEndField\"],\n })\n // `calendarEndField` must also name a real `date`/`datetime` field — same parse.\n .refine(calendarEndFieldIsDateLike, {\n message: \"schema `calendarEndField` must name a top-level `date` or `datetime` field declared in `fields`\",\n path: [\"calendarEndField\"],\n })\n // `calendarTimeField` places records on the day view, so it only means\n // something alongside a start anchor.\n .refine(calendarTimeFieldRequiresCalendarField, {\n message: \"schema `calendarTimeField` requires `calendarField` (it supplies the time-of-day for the calendar's day view)\",\n path: [\"calendarTimeField\"],\n })\n // `calendarTimeField` must name a real top-level field (a free-form time\n // string the day view parses).\n .refine(calendarTimeFieldIsDeclared, {\n message: \"schema `calendarTimeField` must name a top-level field declared in `fields`\",\n path: [\"calendarTimeField\"],\n })\n // …and that field must be string-backed — the day view parses its value as a\n // time string, so a number/enum/date column can't drive it.\n .refine(calendarTimeFieldIsStringBacked, {\n message: \"schema `calendarTimeField` must name a top-level `string` or `text` field declared in `fields`\",\n path: [\"calendarTimeField\"],\n })\n // `kanbanField` must name a real `enum` field — the board groups records\n // into one column per declared enum value; any other type has no closed\n // set of columns to group by.\n .refine(kanbanFieldIsAnEnum, {\n message: \"schema `kanbanField` must name a top-level `enum` field declared in `fields`\",\n path: [\"kanbanField\"],\n })\n // A `toggle` field projects an `enum` field: its `field` must name a real\n // top-level enum, and `onValue` / `offValue` must be members of that\n // enum's `values` — otherwise toggling would write a value outside the\n // closed set (and never appear \"checked\").\n .refine(togglesProjectValidEnums, {\n message: \"a `toggle` field's `field` must name a top-level `enum` field, and its `onValue`/`offValue` must be values of that enum\",\n path: [\"fields\"],\n })\n // `notifyWhen` narrows the completion bell, so it only means something with\n // completion tracking, and its `field` must name a real top-level field.\n .refine(notifyWhenRequiresCompletion, {\n message: \"schema `notifyWhen` requires `completionField` (it narrows that bell)\",\n path: [\"notifyWhen\"],\n })\n .refine(notifyWhenFieldIsDeclared, {\n message: \"schema `notifyWhen.field` must name a top-level field declared in `fields`\",\n path: [\"notifyWhen\"],\n })\n // Every custom view `id` must be a valid slug — it doubles as the\n // view-mode selector key (`custom:<id>`) and the capability-token clamp\n // key, both of which expect a path-safe token.\n .refine(viewIdsAreSlugs, {\n message: \"every `views[].id` must be a valid slug (alphanumeric / hyphen / underscore, no path separators)\",\n path: [\"views\"],\n })\n // Custom view ids must be unique so the selector + token clamp resolve\n // unambiguously.\n .refine(viewIdsAreUnique, {\n message: \"schema `views` must have unique `id`s\",\n path: [\"views\"],\n });\n\n// ---------------------------------------------------------------------------\n// Prototype-sensitive field names — checked on the RAW input\n// ---------------------------------------------------------------------------\n\n// A field name becomes a plain-object key THROUGHOUT the engine — record\n// JSON, edit drafts, enrichment output, view filter state — where a\n// prototype-sensitive name would read (or write) inherited prototype data\n// instead of field data (Codex review on PR #2176: a flag named\n// `__proto__` jams its filter chip). This check must run BEFORE zod\n// parses: zod's record builder silently SKIPS an own `__proto__` input\n// key (pollution safety), so a post-parse refine would never see it and\n// the author's field would just vanish. Hence the `z.preprocess` wrapper\n// below rather than another `.refine`.\nconst PROTOTYPE_KEYS = [\"__proto__\", \"constructor\", \"prototype\"] as const;\n\n/** The first own prototype-sensitive key of `value`, or null. */\nfunction ownPrototypeKey(value: unknown): string | null {\n if (value === null || typeof value !== \"object\") return null;\n for (const key of PROTOTYPE_KEYS) {\n if (Object.hasOwn(value, key)) return key;\n }\n return null;\n}\n\n/** Own enumerable entries of an object (arrays keyed by index), none for\n * anything else — the raw input is unvalidated, so `fields` may be junk. */\nfunction ownEntries(value: unknown): [string, unknown][] {\n if (isUnknownArray(value)) return value.map((entry, index): [string, unknown] => [String(index), entry]);\n return isRecord(value) ? Object.entries(value) : [];\n}\n\n/** The name-defining sub-record a raw field spec (`of`) or action (`params`)\n * carries, or undefined when the holder isn't an object at all. */\nfunction nameDefiningSubRecord(holder: unknown, key: \"of\" | \"params\"): unknown {\n return isRecord(holder) ? holder[key] : undefined;\n}\n\n/** Dotted path of the first prototype-sensitive `params` name across both\n * action lists, or null. */\nfunction prototypeActionParamPath(input: Record<string, unknown>): string | null {\n for (const [listName, list] of [\n [\"actions\", input.actions],\n [\"collectionActions\", input.collectionActions],\n ] as const) {\n for (const action of isUnknownArray(list) ? list : []) {\n const badParam = ownPrototypeKey(nameDefiningSubRecord(action, \"params\"));\n if (badParam !== null) return `${listName}.params.${badParam}`;\n }\n }\n return null;\n}\n\n/** Dotted path of the first prototype-sensitive field name in the raw\n * schema input — top-level `fields`, each table field's `of`, and each\n * action's `params` (the three records that DEFINE names) — or null. */\nfunction prototypeFieldKeyPath(input: unknown): string | null {\n if (!isRecord(input)) return null;\n const bad = ownPrototypeKey(input.fields);\n if (bad !== null) return `fields.${bad}`;\n for (const [key, spec] of ownEntries(input.fields)) {\n const badSub = ownPrototypeKey(nameDefiningSubRecord(spec, \"of\"));\n if (badSub !== null) return `fields.${key}.of.${badSub}`;\n }\n return prototypeActionParamPath(input);\n}\n\nexport const CollectionSchemaZ = z.preprocess((input, ctx) => {\n const bad = prototypeFieldKeyPath(input);\n if (bad !== null) {\n ctx.addIssue({ code: \"custom\", message: `'${bad}': field names must not be prototype-sensitive keys (\\`__proto__\\`, \\`constructor\\`, \\`prototype\\`)` });\n return z.NEVER;\n }\n return input;\n}, BareCollectionSchemaZ);\n","// Discover schema-driven collections. A \"collection\" is a skill\n// directory that ships a `schema.json` alongside its `SKILL.md`.\n// Scans both user (`~/.claude/skills/`) and project\n// (`<workspace>/.claude/skills/`) scopes; project wins on slug\n// collision (mirrors the rule in\n// `server/workspace/skills/discovery.ts`). A host may declare a root to\n// have NO user scope (`paths.userSkillsDir` → null), and then there is no\n// shadowing to reason about: that root sees its own collections and feeds,\n// and nothing else.\n//\n// The schema validator itself lives in `../core/schemaZ` (the zod single\n// source of truth every `../core/schema` type derives from); this module\n// applies it, plus the post-Zod acceptance gates below.\n\nimport { readdir, readFile, stat } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { log, getWorkspaceRoot, userSkillsDir, projectSkillsDir, feedsRoot, hostSupportsSharedCollections } from \"./host\";\nimport { CollectionSchemaZ } from \"../core/schemaZ\";\nimport { SCHEMA_FILE, resolveDataDir, safeSlugName } from \"./paths\";\nimport { appManifestReason, loadAppManifest } from \"./appManifest\";\nimport type { LoadedCollection } from \"./discoveredCollection\";\nimport type { CollectionDetail, CollectionSchema, CollectionSource, CollectionSummary } from \"../core/schema\";\nimport { isErrorWithCode, isRecord } from \"@mulmoclaude/common\";\n\n// Re-exported for the existing `collection/server` importers (manageCollection's\n// putSchema, the registry importWriter) that validate schemas the same way\n// discovery does.\nexport { CollectionSchemaZ };\n\n// The LoadedCollection shape now lives in @mulmoclaude/core/collection/server\n// (imported at the top, re-exported below) so discovery stays its producer and\n// the many `from \"./discovery.js\"` importers resolve it unchanged.\n\n// Normalize an agent-authored feed schema (no register tool to do it):\n// default `icon`, and **force** `dataPath` to the feed-owned namespace\n// `data/feeds/<slug>`. Forcing dataPath (rather than trusting the file) is\n// a safety boundary — a feed can only ever read/write/delete records under\n// its own folder, never another app's data (e.g. `data/wiki`). Non-object\n// input passes through so the Zod error stays clear.\nfunction applyFeedSchemaDefaults(parsed: unknown, slug: string): unknown {\n if (!isRecord(parsed)) return parsed;\n const icon = typeof parsed.icon === \"string\" && parsed.icon.trim().length > 0 ? parsed.icon : \"dynamic_feed\";\n return { ...parsed, icon, dataPath: `data/feeds/${slug}` };\n}\n\n/** Result of the post-Zod acceptance gates: the resolved record dir (and,\n * for a `dataSource` schema, the resolved data file) on success, or a\n * one-line reason discovery would skip the schema. */\nexport type SchemaAcceptance = { ok: true; dataDir: string; dataSourceFile?: string; storageFile?: string; appId?: string } | { ok: false; reason: string };\n\n/** The conventional per-slug records dir a `dataSource` / `storage` collection\n * gets as its `dataDir` (records never live there, but archive/delete paths\n * stay well-defined — same shape the registry's R3 normalization uses).\n *\n * INVARIANT — this is NOT a default `dataPath`, and must not be used as one.\n * It applies only to the two backends whose records are not per-file JSON. A\n * normal collection declares its own location and exactly one of `dataPath` /\n * `dataSource` / `storage`; a schema with none of the three is REJECTED, not\n * quietly pointed here. Handing a per-file collection this path would silently\n * relocate its records away from the folder the user (and its SKILL.md) sees. */\nfunction conventionalDataPath(slug: string): string {\n return `data/collections/${slug}/items`;\n}\n\n/** The declared field named by `primaryKey`, or `undefined` when the schema\n * declares no such field. Own-property guarded: a `primaryKey` of `toString`\n * / `constructor` / `__proto__` must miss here, not read an Object.prototype\n * member and slip past the \"is it a declared field?\" gate into the wrong\n * \"add `primary: true`\" advice. Shared with manageCollection's putSchema\n * gate so both report the SAME reason. */\nexport function resolvePrimaryField(fields: CollectionSchema[\"fields\"], primaryKey: string): CollectionSchema[\"fields\"][string] | undefined {\n return Object.hasOwn(fields, primaryKey) ? fields[primaryKey] : undefined;\n}\n\n/** The acceptance gates discovery applies AFTER `CollectionSchemaZ` parses,\n * before a schema becomes a live collection:\n *\n * - the `primaryKey` must be a declared field flagged `primary: true` —\n * without the flag CollectionView renders the field editable, and a\n * rename is silently pinned back to the URL itemId on save, so the user's\n * edit is dropped with no error;\n * - a `feed` schema must declare an `ingest` block (else it's a dead,\n * non-refreshable card);\n * - `dataPath` — or a `dataSource`'s `path` — must resolve INSIDE the\n * workspace (same realpath containment for both).\n *\n * Exported so `manageCollection`'s `putSchema` can run the SAME gates before\n * it reports success — a schema that passes `CollectionSchemaZ` but fails one\n * of these would otherwise write cleanly yet be skipped on the next discovery,\n * hiding the collection (the exact failure that tool exists to prevent). */\nexport function acceptParsedSchema(schema: CollectionSchema, opts: { source: CollectionSource; workspaceRoot: string; slug: string }): SchemaAcceptance {\n const primaryField = resolvePrimaryField(schema.fields, schema.primaryKey);\n if (!primaryField) return { ok: false, reason: `primaryKey '${schema.primaryKey}' is not one of the declared fields` };\n if (primaryField.primary !== true) return { ok: false, reason: `the primaryKey field '${schema.primaryKey}' must be flagged \\`primary: true\\`` };\n if (opts.source === \"feed\" && !schema.ingest) return { ok: false, reason: \"a feed schema must declare an `ingest` block\" };\n if (schema.dataSource !== undefined) {\n // Same containment math as dataPath — resolveDataDir doesn't require\n // the target to exist, so it validates a file path just as well.\n const dataSourceFile = resolveDataDir(schema.dataSource.path, opts.workspaceRoot);\n if (dataSourceFile === null) return { ok: false, reason: `dataSource.path '${schema.dataSource.path}' escapes the workspace` };\n const dataDir = resolveDataDir(conventionalDataPath(opts.slug), opts.workspaceRoot);\n if (dataDir === null) return { ok: false, reason: `slug '${opts.slug}' yields no workspace-contained data dir` };\n return { ok: true, dataDir, dataSourceFile };\n }\n if (schema.storage !== undefined) return acceptStorageSchema(schema.storage, opts);\n const dataDir = resolveDataDir(schema.dataPath ?? \"\", opts.workspaceRoot);\n if (dataDir === null) return { ok: false, reason: `dataPath '${schema.dataPath}' escapes the workspace` };\n return { ok: true, dataDir };\n}\n\n/** The `storage` arm of the acceptance gate. Every storage backend gets the\n * conventional phantom dataDir; what differs is what else has to resolve\n * before the collection can exist at all.\n *\n * A FILE-backed backend (sqlite) resolves and containment-checks a\n * `storageFile`. A SHARED one (firestore) has no path on this machine — it\n * resolves an IDENTITY instead: the `aid` from the repository's `app.json`,\n * which together with the slug as `cid` names `apps/{aid}/collections/{cid}`.\n *\n * Resolving it HERE, once, is the point. The store then receives a settled\n * `(aid, cid)` and never reads `app.json` itself — otherwise the questions of\n * caching, staleness and what to do when the file is missing would be decided\n * inside a read path, where the only cheap answer is to return nothing, and\n * \"this collection is misconfigured\" would reach the user as \"this collection\n * is empty\". A missing or malformed `app.json` is a CONFIGURATION error, so it\n * is reported the same way an escaping `storage.path` is: the schema is\n * refused, with a reason naming the file to create. */\nfunction acceptStorageSchema(storage: NonNullable<CollectionSchema[\"storage\"]>, opts: { workspaceRoot: string; slug: string }): SchemaAcceptance {\n const dataDir = resolveDataDir(conventionalDataPath(opts.slug), opts.workspaceRoot);\n if (dataDir === null) return { ok: false, reason: `slug '${opts.slug}' yields no workspace-contained data dir` };\n if (storage.type === \"sqlite\") {\n const storageFile = resolveDataDir(storage.path, opts.workspaceRoot);\n if (storageFile === null) return { ok: false, reason: `storage.path '${storage.path}' escapes the workspace` };\n return { ok: true, dataDir, storageFile };\n }\n // A shared collection needs a host that can reach Firestore AND a host whose\n // roots are project repositories (D5). Both are the same question from the\n // engine's side, and the host answers it: see `CollectionHost.sharedCollections`.\n // Refused rather than skipped, so the author is told why instead of watching\n // the collection vanish from discovery.\n if (!hostSupportsSharedCollections()) {\n return { ok: false, reason: \"this host does not support shared collections — they live in a project repository, not a managed workspace\" };\n }\n const manifest = loadAppManifest(opts.workspaceRoot);\n if (!manifest.ok) return { ok: false, reason: appManifestReason(manifest, opts.workspaceRoot) };\n return { ok: true, dataDir, appId: manifest.manifest.aid };\n}\n\nasync function loadOneCollection(skillsRoot: string, slug: string, source: CollectionSource, workspaceRoot: string): Promise<LoadedCollection | null> {\n const safeName = safeSlugName(slug);\n if (safeName === null) return null;\n const schemaPath = path.join(skillsRoot, safeName, SCHEMA_FILE);\n let raw: string;\n try {\n const fileStat = await stat(schemaPath);\n if (!fileStat.isFile()) return null;\n raw = await readFile(schemaPath, \"utf-8\");\n } catch (err) {\n if (!isErrorWithCode(err) || err.code !== \"ENOENT\") {\n log.warn(\"collections\", \"failed to read schema.json, skipping\", { slug: safeName, path: schemaPath, error: String(err) });\n }\n return null;\n }\n\n let parsedJson: unknown;\n try {\n parsedJson = JSON.parse(raw);\n } catch (err) {\n log.warn(\"collections\", \"schema.json is not valid JSON, skipping\", { slug: safeName, error: String(err) });\n return null;\n }\n\n // Feeds are authored by the agent as plain files (no register tool), so\n // fill the boilerplate icon / dataPath if omitted before validation.\n const candidate = source === \"feed\" ? applyFeedSchemaDefaults(parsedJson, safeName) : parsedJson;\n const parsed = CollectionSchemaZ.safeParse(candidate);\n if (!parsed.success) {\n log.warn(\"collections\", \"schema.json failed validation, skipping\", { slug: safeName, issues: parsed.error.issues });\n return null;\n }\n\n // Post-Zod acceptance gates (primaryKey flagged primary, feed ingest,\n // workspace-contained dataPath) — shared with manageCollection's putSchema\n // so a validated write and discovery agree on what's a live collection.\n const schema = parsed.data;\n const acceptance = acceptParsedSchema(schema, { source, workspaceRoot, slug: safeName });\n if (!acceptance.ok) {\n log.warn(\"collections\", \"schema.json rejected after validation, skipping\", { slug: safeName, reason: acceptance.reason });\n return null;\n }\n\n return {\n slug: safeName,\n source,\n schema,\n dataDir: acceptance.dataDir,\n ...(acceptance.dataSourceFile !== undefined ? { dataSourceFile: acceptance.dataSourceFile } : {}),\n ...(acceptance.storageFile !== undefined ? { storageFile: acceptance.storageFile } : {}),\n ...(acceptance.appId !== undefined ? { appId: acceptance.appId } : {}),\n skillDir: path.join(skillsRoot, safeName),\n };\n}\n\nasync function collectFromDir(skillsRoot: string, source: CollectionSource, workspaceRoot: string): Promise<LoadedCollection[]> {\n let entries: string[];\n try {\n entries = await readdir(skillsRoot);\n } catch (err) {\n if (isErrorWithCode(err) && err.code === \"ENOENT\") return [];\n log.warn(\"collections\", \"failed to list skills dir, returning empty\", { root: skillsRoot, error: String(err) });\n return [];\n }\n\n const results: LoadedCollection[] = [];\n for (const name of entries) {\n if (name.startsWith(\".\")) continue;\n const safeName = safeSlugName(name);\n if (safeName === null) continue;\n const dirPath = path.join(skillsRoot, safeName);\n let dirStat;\n try {\n dirStat = await stat(dirPath);\n } catch {\n continue;\n }\n if (!dirStat.isDirectory()) continue;\n const collection = await loadOneCollection(skillsRoot, safeName, source, workspaceRoot);\n if (collection) results.push(collection);\n }\n return results;\n}\n\n/** The user-scope dir this call should scan, or `null` for none. The single\n * place the \"explicit override beats the host binding, and either may say\n * none\" rule is spelled — `??` cannot express it, because `undefined` there\n * means \"ask the host\" and would silently re-enable a scope the caller\n * passed `null` to switch off. */\nfunction resolveUserDir(opts: DiscoveryOptions, workspaceRoot: string): string | null {\n return opts.userSkillsDir !== undefined ? opts.userSkillsDir : userSkillsDir(workspaceRoot);\n}\n\nexport interface DiscoveryOptions {\n /** Override the workspace root for project-scope skill discovery.\n * Default: the live `workspacePath`. Tests point this at a\n * `mkdtempSync` tree so they don't touch the user's real\n * `~/mulmoclaude/`. Mirrors the pattern in\n * `server/workspace/skills/catalog.ts#CatalogOptions`. */\n workspaceRoot?: string | undefined;\n /** Override `~/.claude/skills/` for tests. Production callers\n * leave this unset. Without an override, even a test-scoped\n * workspaceRoot still scans the real user home — which can leak\n * unrelated skills into the result.\n *\n * Three distinct values, and the difference matters — a caller that\n * thinks it opted out and did not is exactly the failure the scope\n * isolation removes:\n * - `undefined` (or absent): ask the host binding for this root, which\n * may itself answer `null`.\n * - a path: scan that dir as user scope.\n * - `null`: this call has NO user scope. The host is not consulted. */\n userSkillsDir?: string | null | undefined;\n}\n\n/** Discover every schema-driven collection available to this\n * workspace. Project-scope collections override user-scope on slug\n * collision. The `workspaceRoot` override also flows into each\n * collection's dataDir resolution so a tmpdir-scoped test gets\n * dataDirs under the same tmpdir (Codex P1 review on PR #1489 —\n * previously dataDir was always rooted at the live workspacePath\n * regardless of override). */\nexport async function discoverCollections(opts: DiscoveryOptions = {}): Promise<LoadedCollection[]> {\n const workspaceRoot = opts.workspaceRoot ?? getWorkspaceRoot();\n const userDir = resolveUserDir(opts, workspaceRoot);\n const projectDir = projectSkillsDir(workspaceRoot);\n // Feeds (the non-skill `<workspace>/feeds/` registry) are scanned as a\n // third root. They merge FIRST so a real skill collection (user or\n // project) always overrides a feed on slug collision — a feed must\n // never shadow a genuine skill-backed collection.\n const feedCollections = await collectFromDir(feedsRoot(workspaceRoot), \"feed\", workspaceRoot);\n // A root with no user scope skips the pass entirely (not an empty dir scan)\n // — see the `userSkillsDir` contract in `host.ts`.\n const userCollections = userDir === null ? [] : await collectFromDir(userDir, \"user\", workspaceRoot);\n const projectCollections = await collectFromDir(projectDir, \"project\", workspaceRoot);\n const merged = new Map<string, LoadedCollection>();\n for (const entry of feedCollections) merged.set(entry.slug, entry);\n for (const entry of userCollections) merged.set(entry.slug, entry);\n for (const entry of projectCollections) merged.set(entry.slug, entry);\n return [...merged.values()].sort((left, right) => left.slug.localeCompare(right.slug));\n}\n\n/** Load one collection by slug. Returns null if the slug is invalid,\n * no matching skill exists, or the schema is malformed. */\nexport async function loadCollection(slug: string, opts: DiscoveryOptions = {}): Promise<LoadedCollection | null> {\n const safeName = safeSlugName(slug);\n if (safeName === null) return null;\n const workspaceRoot = opts.workspaceRoot ?? getWorkspaceRoot();\n const userDir = resolveUserDir(opts, workspaceRoot);\n const projectDir = projectSkillsDir(workspaceRoot);\n // Project first (overrides user), then user, then the feeds registry\n // last — mirroring the merge precedence in `discoverCollections` so a\n // skill collection always wins over a feed of the same slug.\n const projectCollection = await loadOneCollection(projectDir, safeName, \"project\", workspaceRoot);\n if (projectCollection) return projectCollection;\n // No user scope for this root: skip the fallback, so a slug that exists\n // ONLY in user scope is a MISS rather than a quiet hop into another world.\n const userCollection = userDir === null ? null : await loadOneCollection(userDir, safeName, \"user\", workspaceRoot);\n if (userCollection) return userCollection;\n return loadOneCollection(feedsRoot(workspaceRoot), safeName, \"feed\", workspaceRoot);\n}\n\nexport function toSummary(collection: LoadedCollection): CollectionSummary {\n return {\n slug: collection.slug,\n title: collection.schema.title,\n icon: collection.schema.icon,\n source: collection.source,\n ...(collection.schema.dataSource !== undefined ? { readonly: true as const } : {}),\n ...(collection.appId !== undefined ? { appId: collection.appId } : {}),\n };\n}\n\nexport function toDetail(collection: LoadedCollection): CollectionDetail {\n return { ...toSummary(collection), schema: collection.schema };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"discovery-D88x2DmW.js","names":[],"sources":["../src/host/hostSlot.ts","../src/collection/server/host.ts","../src/collection/server/backendAvailability.ts","../src/collection/server/paths.ts","../src/collection/server/storePage.ts","../src/collection/server/firestoreStore.ts","../src/collection/server/appManifest.ts","../src/collection/server/io.ts","../src/collection/core/queryZ.ts","../src/collection/server/csvQuery.ts","../src/collection/server/csvStore.ts","../src/collection/server/watchFs.ts","../src/collection/server/sqliteStore.ts","../src/collection/server/store.ts","../src/collection/core/mutateAction.ts","../src/collection/core/schemaRules.ts","../src/collection/core/schemaZ.ts","../src/collection/server/discovery.ts"],"sourcesContent":["// Shared host-injection primitives for the server-side engines. Each engine is\n// parameterized over host-owned dependencies (workspace root, logger, file\n// writer, …) that would be an uphill import if pulled in directly. Instead the\n// host wires them ONCE at startup and the engine reads them back through a slot.\n// This file centralises the storage/throw/no-op contract so the three domain\n// bindings (collection, feeds, google) can't drift into subtly different\n// behaviour.\n\nimport type { StructuredLogger } from \"@mulmoclaude/common\";\n\n/** Structured logger shape the engines log through — `(prefix, message,\n * data?)`, matching the host `Logger`. The canonical declaration lives in\n * `@mulmoclaude/common`; the domains alias it under their public names\n * (`CollectionLogger` / `FeedsLogger` / `GoogleLogger`). */\nexport type { StructuredLogger };\n\n/** A single host-injected dependency, wired once at boot and read back by the\n * engine. `get()` fails loudly when the host never configured (the engine\n * cannot operate without it); `peek()` stays quiet for non-critical reads. */\nexport interface HostSlot<T> {\n /** Wire the value. Re-binding to a *different* value throws — silently\n * redirecting a configured engine to another host would be a bug. Re-binding\n * the same value is a no-op. */\n set: (value: T) => void;\n /** The configured value, or throw (with the slot name) if never set. */\n get: () => T;\n /** The configured value, or `null` if never set — never throws. */\n peek: () => T | null;\n /** Test-only: forget the configured value. */\n reset: () => void;\n}\n\nexport function createHostSlot<T>(name: string): HostSlot<T> {\n let current: T | null = null;\n const set = (value: T): void => {\n if (current !== null && current !== value) {\n throw new Error(`${name} was already called with a different host`);\n }\n current = value;\n };\n const get = (): T => {\n if (current === null) throw new Error(`${name} was not called by the host`);\n return current;\n };\n return { set, get, peek: () => current, reset: () => (current = null) };\n}\n\n/** Build the 4-method forwarding logger over a `() => StructuredLogger | null`\n * getter. Calls made before a logger is available are dropped — logging is\n * non-critical, unlike a required getter which fails loudly. Centralised so no\n * domain can drift into throwing where a sibling drops. */\nexport function createForwardingLogger(getLogger: () => StructuredLogger | null): StructuredLogger {\n return {\n error: (prefix, message, data) => getLogger()?.error(prefix, message, data),\n warn: (prefix, message, data) => getLogger()?.warn(prefix, message, data),\n info: (prefix, message, data) => getLogger()?.info(prefix, message, data),\n debug: (prefix, message, data) => getLogger()?.debug(prefix, message, data),\n };\n}\n","// Host binding for the server-side collection engine.\n//\n// The engine is parameterized over the host's workspace + services, but\n// threading those through every call would be invasive. Instead each host\n// (MulmoClaude, MulmoTerminal) configures the binding ONCE at startup via\n// `configureCollectionHost`, and the engine reads it through the getters\n// below. This keeps the existing call sites (which default to the live\n// workspace root) unchanged while removing the package's dependency on\n// host-only modules (`server/workspace/workspace.ts`, the host logger).\n\nimport path from \"node:path\";\nimport { canonicalRoot } from \"../../files/root.js\";\nimport { localCollectionKeyOf, sharedCollectionKey, type CollectionKey } from \"../core/collectionKey.js\";\nimport { createForwardingLogger, createHostSlot, type StructuredLogger } from \"../../host/hostSlot.js\";\n// Type-only: this module must not pull the firebase SDK in at runtime (it is an\n// OPTIONAL peer of this package). The adapter ships from\n// `@mulmoclaude/core/collection/firestore` instead.\nimport type { FirestoreDocs } from \"./firestoreDocs\";\n\n/** Public alias of the shared `StructuredLogger` — keeps the domain surface name-stable. */\nexport type CollectionLogger = StructuredLogger;\n\n/** Re-exported so collection-engine callers (the watcher, the reconciler) keep\n * one import surface; the definition lives in `@mulmoclaude/core/files`\n * because a root is an identity in subsystems that do not depend on this one. */\nexport { canonicalRoot };\n\n/** `err.code` on the throw from `getWorkspaceRoot()` under an explicit-root\n * binding: this CALL is missing its `workspaceRoot` option. Exported because\n * the fix differs from a watcher root conflict — that one means another root's\n * watcher is already running — and a host catching both in one place should\n * not have to match on message text to tell them apart. */\nexport const COLLECTION_ROOT_REQUIRED = \"COLLECTION_ROOT_REQUIRED\";\n\n/** Build a local {@link CollectionKey}, canonicalising the root. The identity\n * type itself is isomorphic and cannot canonicalise (that needs `node:path`),\n * so this is the constructor server code should use. */\nexport const localCollectionKey = (root: string, slug: string): CollectionKey => localCollectionKeyOf(canonicalRoot(root), slug);\n\n/** INVARIANT — **a slug is unique within a root and nowhere else.**\n *\n * A collection's identity is `(root, slug)`. Anything keyed by slug ALONE —\n * a cache, a pubsub channel, a view token, a notification id, a rendered\n * card — is a cross-root collision waiting to happen the moment two projects\n * each own a `tasks` collection. A single-workspace host never sees it, which\n * is exactly why it keeps being written that way.\n *\n * Every engine surface that crosses a host boundary therefore carries the\n * root: `CollectionChangePayload.root`, the completion bell's legacy id, the\n * presented card's scope. When you add another, key it on the pair. */\nexport interface CollectionHost {\n /** Absolute path to the host workspace root (e.g. `~/mulmoclaude`). The\n * default root for every path/containment check that isn't given an\n * explicit override.\n *\n * `null` puts the binding in EXPLICIT-ROOT mode: the host declares that it\n * always passes `opts.workspaceRoot` per call, and `getWorkspaceRoot()`\n * throws instead of guessing. A multi-root host (MulmoTerminal, one root\n * per project) wants this — there, a forgotten option is not a crash but a\n * silent read/write against the WRONG project. A single-workspace host\n * (MulmoClaude) passes a string and nothing changes. */\n workspaceRoot: string | null;\n /** Host logger; the engine logs under the `\"collections\"` prefix. */\n log: CollectionLogger;\n /** Host workspace layout — supplied as the host's own path helpers so the\n * package owns no layout literals and works against a test/alt root. */\n paths: {\n /** Absolute user-scope skills dir for a root (host-specific, e.g.\n * `~/.claude/skills`), or `null` when this root has NO user scope.\n *\n * A single-workspace host (MulmoClaude) returns the same path for its one\n * root and behaves exactly as before — user scope merges into the\n * workspace and a project slug still shadows a user one.\n *\n * A multi-root host returns `null` for a plain project directory: `~` and\n * a project are separate worlds, and a project that could resolve a\n * machine-global collection would depend on something no clone of it can\n * have. Under `null` the user pass is skipped in BOTH discovery and\n * `loadCollection` — a user-only slug is then a miss, not a quiet hop\n * into another world. Resolution is where the guarantee has to hold:\n * filtering only the listing would still let a slug typed by the agent,\n * or arriving in a URL, write into `~/.claude/skills` from a project.\n *\n * A bare `string` is the pre-3.3.0 form and still works, read as \"this\n * path, for every root\". It is accepted rather than required-away because\n * a caret range floats across minors: a host pinned at `^3.2.0` installs\n * this version without touching its code, and a required callable would\n * turn that into a TypeError on its first discovery — a crash, not an\n * opt-out. Prefer the function form; the string is deprecated. */\n userSkillsDir: string | ((workspaceRoot: string) => string | null);\n /** Absolute project-scope skills dir for a workspace (`<root>/.claude/skills`). */\n projectSkillsDir: (workspaceRoot: string) => string;\n /** Absolute feeds-registry root for a workspace (`<root>/data/feeds`). */\n feedsRoot: (workspaceRoot: string) => string;\n /** Absolute project-skills *staging* dir for a root (`<root>/data/skills`),\n * or `null` when this root has NO staging tree.\n *\n * Staging exists because a managed workspace gates writes into `.claude/`\n * and a skill-bridge hook mirrors `data/skills/<slug>/` across. A root\n * with no such bridge (a plain project folder) has nothing to mirror, so\n * the skill dir IS the authoring location and there is no staging.\n *\n * Return `null` there — do NOT hand back the skill dir instead. It looks\n * equivalent (the read list becomes the same dir twice) but the delete\n * path `rm -rf`s the staging dir by name, which would then remove the\n * committed skill under the label \"staging\". */\n skillsStagingDir: (workspaceRoot: string) => string | null;\n /** Workspace-relative archive dir (a removed collection's files move here). */\n archiveDir: string;\n /** Absolute path to the user-supplied extra-registries config file for a\n * workspace (`<root>/config/collections-registries.json`). Injected so the\n * registry engine owns no app layout literal and a downstream host can point\n * it at its own workspace. */\n collectionsRegistriesConfig: (workspaceRoot: string) => string;\n };\n /** True for a preset-skill slug (host-owned naming convention). */\n isPresetSlug: (slug: string) => boolean;\n}\n\n/** The authenticated Firestore access a shared collection is served through.\n *\n * It carries NO uid, and that is a decision rather than an omission. Nothing\n * about a shared collection is keyed by uid any more: the documents live at\n * `apps/{aid}/collections/{cid}/items`, and the deployed rules authorize on\n * `request.auth.token.email` against the app's member roster. A uid kept\n * \"just for identity\" would be a value nothing checks, sitting next to a path\n * it no longer determines — which is how a later reader ends up deriving a\n * path from it again.\n *\n * `email` is what the rules actually evaluate, so it is what makes a refusal\n * explainable: `permission-denied` is the most common failure a shared\n * collection has and the least informative, and naming the principal turns it\n * into \"signed in as a@b — this app's roster may not list you\". A session with\n * no verified email is NOT a session here (the accessor answers null): public\n * anonymous submission is a visitor's path through the published web app, not\n * the host's.\n *\n * `docs` is the narrow document interface rather than a raw `Firestore` so the\n * backend stays testable: core ships `createFirestoreDocs` over the real SDK\n * (`firestoreDocs.ts`) and tests inject an in-memory fake. */\nexport interface FirestoreHandle {\n docs: FirestoreDocs;\n email: string;\n /** The signed-in Firebase uid.\n *\n * The EMAIL is the principal the roster is keyed by, and it is what every\n * record operation needs. The uid is needed by exactly one caller —\n * `publish` — because the app document's `owner` is a uid: the rules\n * require `owner == request.auth.uid` when the app is created and require\n * it unchanged afterwards. Required rather than optional so a host cannot\n * wire a session that can read and write records but silently cannot\n * create an app; the failure would surface as a permission denial with\n * nothing in it about identity. */\n uid: string;\n}\n\n/** A collection's records changed on disk. Carries the `slug` so the host can\n * publish on a per-collection channel; `ids` lists the affected record ids\n * when known (a consumer may ignore them and refetch the whole collection),\n * and `op` is advisory. Deliberately carries NO record bodies — this is a\n * \"refetch\" ping, not a data feed, so it stays cheap and leaks nothing when a\n * host relays it into an opaque-origin custom-view iframe. */\n/** Fields every change carries, whatever kind of collection it is about. */\ninterface CollectionChangeBase {\n /** The collection's NAME in its scope: the slug, or a shared collection's\n * `cid`. Never an identity on its own — see the two arms below. */\n slug: string;\n ids?: string[];\n op?: \"upsert\" | \"delete\";\n}\n\n/** A change to a collection in a directory. */\nexport interface LocalCollectionChange extends CollectionChangeBase {\n /** Absolute workspace/project root the change happened under. Present only\n * when the engine call carried an explicit `workspaceRoot`; absent means the\n * host's configured root, so a single-workspace host never sets it.\n * A multi-root host MUST key its live-update fan-out on `(root, slug)` —\n * two projects each owning a `tasks` collection would otherwise refresh each\n * other's open views. */\n root?: string;\n /** Never on a local change. The two arms are mutually exclusive at the TYPE\n * level, not by convention: a payload carrying both would be read as shared\n * by `collectionChangeKey`, which drops the root and fans the update out on\n * the wrong channel. */\n aid?: never;\n}\n\n/** A change to a collection published to a shared app. */\nexport interface SharedCollectionChange extends CollectionChangeBase {\n /** The shared app (`apps/{aid}/collections/{cid}`). Required on this arm:\n * it is what makes the payload an identity. */\n aid: string;\n /** Never on a shared change — a shared collection has no root, because the\n * same collection is resolved from every clone of the repository. */\n root?: never;\n}\n\n/** A collection's records changed. Carries the `slug` so the host can publish\n * on a per-collection channel; `ids` lists the affected record ids when known\n * (a consumer may ignore them and refetch the whole collection), and `op` is\n * advisory. Deliberately carries NO record bodies — this is a \"refetch\" ping,\n * not a data feed, so it stays cheap and leaks nothing when a host relays it\n * into an opaque-origin custom-view iframe.\n *\n * Reading a payload is unchanged: `slug`, `root`, `ids` and `op` are all\n * reachable on the union. Use {@link collectionChangeKey} rather than deciding\n * what an absent field means by hand. */\nexport type CollectionChangePayload = LocalCollectionChange | SharedCollectionChange;\n\ntype CollectionChangePublisher = (payload: CollectionChangePayload) => void;\n\n/** Build a change payload, attaching `root` only when the engine call carried\n * an explicit one. Centralised so every publish site states the root the same\n * way, and so a single-workspace host's payload shape stays byte-identical to\n * what it saw before multi-root support. */\nexport function collectionChangePayload(base: CollectionChangeBase, root: string | undefined): LocalCollectionChange {\n // Canonical, because a host keys its live-update fan-out on this value: a\n // direct `writeItem({ workspaceRoot: \"/proj/\" })` and the watcher's own\n // publish for `/proj` must land on the same channel, not two.\n return root === undefined ? base : { ...base, root: canonicalRoot(root) };\n}\n\n/** Build a change payload for a SHARED collection. `slug` carries the `cid`,\n * which is what it is called inside its app; `aid` is what makes it an\n * identity. Never stamps a `root` — a shared collection does not have one. */\nexport function sharedCollectionChangePayload(base: CollectionChangeBase, aid: string): SharedCollectionChange {\n // Validated HERE, at construction, not at publish. A host's publisher wraps\n // its publish in a try/catch on purpose -- dropping one live-refresh event\n // beats crashing the write that triggered it -- so a name that no channel can\n // encode would be swallowed there and the update would simply stop arriving,\n // with nothing said. Building the key is the cheap way to say it loudly.\n sharedCollectionKey(aid, base.slug);\n return { ...base, aid };\n}\n\n/** The identity a change is about, as a value — the thing to key a fan-out on.\n *\n * This is the one place that decides what an absent field means, so no host\n * has to: `aid` present is a shared collection, otherwise it is local, and a\n * local payload with no `root` means the host's configured root (which is why\n * a single-workspace host's payloads still say nothing about roots).\n *\n * `fallbackRoot` is what a payload with no root resolves to. An explicit-root\n * host has no such default and must not guess — pass the root the call was\n * made for. */\n/** Refuse a payload that names both an app and a root.\n *\n * The two arms are mutually exclusive in the TYPE, so this cannot come from\n * the constructors -- it is corrupt input (a JS caller, a cast, something off\n * a wire). Throwing rather than picking one: whichever way it were guessed,\n * the update would be fanned out on a channel it does not belong to, and that\n * is invisible where a throw is not.\n *\n * Takes the WIDENED shape deliberately. Written inline, TypeScript narrows the\n * union to `never` inside the branch and the fields cannot be read at all --\n * which is the type-level guarantee doing its job, and exactly why the runtime\n * check has to be stated somewhere it can still see them. */\nfunction requireOneScope(payload: CollectionChangeBase & { aid?: string; root?: string }): void {\n if (payload.aid !== undefined && payload.root !== undefined) {\n throw new Error(`collectionChangeKey: payload for \"${payload.slug}\" carries both an app (${payload.aid}) and a root (${payload.root})`);\n }\n}\n\nexport function collectionChangeKey(payload: CollectionChangePayload, fallbackRoot: string): CollectionKey {\n requireOneScope(payload);\n return payload.aid === undefined ? localCollectionKey(payload.root ?? fallbackRoot, payload.slug) : sharedCollectionKey(payload.aid, payload.slug);\n}\n\nconst hostSlot = createHostSlot<CollectionHost>(\"@mulmoclaude/core/collection/server: configureCollectionHost()\");\nlet changePublisher: CollectionChangePublisher | null = null;\nlet firestoreAccessor: (() => FirestoreHandle | null) | null = null;\nlet sharedCollectionsSupported = false;\n\n/** Wire the engine to a host. Call once at server startup, before any\n * collection storage operation. Re-binding to a *different* host throws —\n * silently redirecting later filesystem operations to another workspace\n * would be a bug, not a feature. Re-calling with the same host is a no-op. */\nexport function configureCollectionHost(host: CollectionHost): void {\n hostSlot.set(host);\n}\n\n/** Wire a publisher that broadcasts record-change events; the host bridges it\n * to its pubsub. Kept SEPARATE from `configureCollectionHost` because the\n * host's pubsub instance isn't ready at host-binding time (the binding is set\n * at the top of server startup, the pubsub later). Optional: left unset, every\n * write is silent — the default for tests and for a host that doesn't want\n * live view updates. Pass `null` to detach (test teardown). */\nexport function setCollectionChangePublisher(publish: CollectionChangePublisher | null): void {\n changePublisher = publish;\n}\n\n/** Broadcast a record-change event if a publisher is wired (no-op otherwise).\n * Called from the write path (`writeItem`/`deleteItem`). The wired publisher is\n * expected to be fire-and-forget (it wraps its own pubsub call in try/catch),\n * so this stays a thin pass-through and never throws into the write. */\nexport function publishCollectionChange(payload: CollectionChangePayload): void {\n changePublisher?.(payload);\n}\n\n/** Does this host serve SHARED (firestore-backed) collections at all?\n *\n * Opt-in, and `false` until a host says otherwise, because the default has to\n * be the safe one: a host that cannot reach Firestore must not accept a\n * shared schema and then report the collection as empty.\n *\n * WHY A CAPABILITY AND NOT A HOST CHECK. The engine must not know which host\n * it is running in. The rule being expressed — shared collections live in a\n * PROJECT REPOSITORY, not in a single managed workspace where one roster\n * would govern every unrelated collection beside it — is a property the host\n * knows about ITSELF. Asking the engine to test for a particular host's\n * workspace would put a host's name in shared code and make every change to\n * a one-host feature a change to this package.\n *\n * WHY NOT A FIELD ON `CollectionHost`. Same reason the accessor below is not\n * one: `configureCollectionHost` is a ONE-SHOT binding a host sets at startup\n * and cannot re-bind, so a suite exercising this engine could not turn the\n * capability on for itself without owning the whole host. This is the same\n * concern as the accessor and belongs beside it.\n *\n * NOT DERIVED FROM THE ACCESSOR. \"Has a session right now\" and \"serves shared\n * collections at all\" are different questions: the accessor answers null\n * between connections, and a collection must not stop being ACCEPTABLE\n * because nobody is signed in — that would turn \"connect first\" into \"this\n * schema is invalid\". */\nexport function setSharedCollectionsSupport(supported: boolean): void {\n sharedCollectionsSupported = supported;\n}\n\n/** Whether the host declared support. Consulted from the schema ACCEPTANCE\n * gate, whose contract is to return a reason rather than raise. */\nexport function hostSupportsSharedCollections(): boolean {\n return sharedCollectionsSupported;\n}\n\n/** Wire the accessor for the host's authenticated Firestore session.\n *\n * Separate from `configureCollectionHost` for the same reason\n * `setCollectionChangePublisher` is: the host binding is set at the top of\n * server startup, but this session doesn't exist until the user connects\n * remote-host (and closes again on disconnect), so it cannot be part of a\n * one-shot binding. Optional — left unwired, only shared collections are\n * affected, and they report \"not connected\". Pass `null` to detach. */\nexport function setFirestoreAccessor(accessor: (() => FirestoreHandle | null) | null): void {\n firestoreAccessor = accessor;\n}\n\n/** The host's live Firestore access, or null when there is no session (or the\n * host never wired one — every non-shared backend leaves it unset).\n * Callers MUST surface null as an actionable \"connect remote-host first\",\n * never as an empty result: silence would be indistinguishable from a\n * collection that genuinely has no records. */\nexport function firestoreHandle(): FirestoreHandle | null {\n return firestoreAccessor?.() ?? null;\n}\n\nfunction requireHost(): CollectionHost {\n return hostSlot.get();\n}\n\n/** The configured workspace root. Throws if the host never configured one —\n * and, under an explicit-root binding (`workspaceRoot: null`), throws rather\n * than guessing, which is the whole point of that mode: on a multi-root host\n * a missing `opts.workspaceRoot` must fail loudly instead of silently\n * resolving against some other project. */\nexport function getWorkspaceRoot(): string {\n const root = requireHost().workspaceRoot;\n if (root === null) {\n throw Object.assign(\n new Error(\n \"@mulmoclaude/core/collection/server: the host is bound in explicit-root mode (workspaceRoot: null), \" +\n \"so there is no ambient workspace root — pass an explicit `workspaceRoot` in this call's options.\",\n ),\n { code: COLLECTION_ROOT_REQUIRED },\n );\n }\n return root;\n}\n\n/** The configured workspace root, or `null` under an explicit-root binding /\n * before the host configures one. Never throws — for callers that need to\n * COMPARE roots (is this the one we are already running for?) rather than\n * operate on one. Anything that will touch the filesystem wants\n * `getWorkspaceRoot()` and its loud failure instead. */\nexport function peekWorkspaceRoot(): string | null {\n return hostSlot.peek()?.workspaceRoot ?? null;\n}\n\n// Workspace-layout accessors — thin wrappers over the host binding, named to\n// match the host helpers they replace so the moved engine modules keep their\n// call sites. Each throws (via requireHost) if the host never configured.\n/** The user-scope skills dir for a root, or `null` when it has none. The one\n * place the pre-3.3.0 `string` binding is normalized, so nothing downstream\n * has to know the host might not have upgraded its shape yet. */\nexport function userSkillsDir(workspaceRoot: string): string | null {\n const binding = requireHost().paths.userSkillsDir;\n return typeof binding === \"string\" ? binding : binding(workspaceRoot);\n}\nexport function projectSkillsDir(workspaceRoot: string): string {\n return requireHost().paths.projectSkillsDir(workspaceRoot);\n}\nexport function feedsRoot(workspaceRoot: string): string {\n return requireHost().paths.feedsRoot(workspaceRoot);\n}\nexport function skillsStagingDir(workspaceRoot: string): string | null {\n return requireHost().paths.skillsStagingDir(workspaceRoot);\n}\n\n/** `<staging>/<slug>` for a root, or `null` when the root has no staging tree.\n * The single place the staging-or-not branch is spelled, so every caller\n * (read bases, schema write targets, archive, delete) agrees on it. */\nexport function stagingSkillDir(workspaceRoot: string, slug: string): string | null {\n const staging = requireHost().paths.skillsStagingDir(workspaceRoot);\n return staging === null ? null : path.join(staging, slug);\n}\nexport function archiveDir(): string {\n return requireHost().paths.archiveDir;\n}\n/** Absolute path to a workspace's `collections-registries.json`. Takes the\n * root explicitly — reading the ambient one here would throw under an\n * explicit-root binding and take the Discover tab down with it. */\nexport function collectionsRegistriesConfigPath(workspaceRoot: string): string {\n return requireHost().paths.collectionsRegistriesConfig(workspaceRoot);\n}\nexport function isPresetSlug(slug: string): boolean {\n return requireHost().isPresetSlug(slug);\n}\n\n/** Logger proxy so engine modules can `import { log }` and use it exactly like\n * the host logger — each call forwards to the live host binding. Logging is\n * non-critical, so calls before the host configures a binding (e.g. unit tests\n * that exercise pure logic) are dropped rather than throwing — unlike\n * `getWorkspaceRoot()`, which fails loudly because the engine cannot operate\n * without a workspace root. */\nexport const log: CollectionLogger = createForwardingLogger(() => hostSlot.peek()?.log ?? null);\n","// \"The backend can't serve this right now\" — as distinct from \"the record\n// isn't there\" or \"the stored record is malformed\".\n//\n// Both non-file backends have such a state, and neither is a data problem:\n//\n// - sqlite: `node:sqlite` needs Node >= 22.5, and the app's floor is 20.12,\n// so on an older runtime ONLY sqlite collections fail (sqliteStore.ts).\n// - csv/dataSource: `@duckdb/node-api` is a native module whose prebuilt\n// binding can be missing for the platform (csvStore.ts).\n//\n// The distinction matters because the layers above catch broadly. Without a\n// type to test, `store.read(...).catch(() => null)` reports \"record missing\",\n// a merge reports \"malformed stored file\", and an ontology count reports 0 —\n// each of which sends the agent after a data problem that does not exist, and\n// the last of which can have it offer to recreate records that are intact.\n//\n// Anything that summarises or swallows a store error MUST re-check with\n// `isBackendUnavailable` and let it through.\n\n/** Thrown by a store when its engine or session cannot serve the request. */\nexport class BackendUnavailableError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"BackendUnavailableError\";\n }\n}\n\nexport function isBackendUnavailable(err: unknown): err is BackendUnavailableError {\n return err instanceof BackendUnavailableError;\n}\n","// Path helpers + safe-slug guard for the collections module. Mirrors the\n// pattern used by `server/workspace/skills/catalog.ts` so CodeQL's\n// `js/path-injection` sanitiser recognises our taint-launder.\n\nimport path from \"node:path\";\nimport { realpathSync } from \"node:fs\";\nimport { getWorkspaceRoot } from \"./host\";\n// The character rules live in ../core/ids so the isomorphic schema validator\n// (../core/schemaZ) gates on the SAME patterns these sanitisers do.\nimport { SAFE_SLUG_PATTERN, SAFE_RECORD_ID_PATTERN } from \"../core/ids\";\nimport { isErrorWithCode } from \"@mulmoclaude/common\";\n\nexport const SCHEMA_FILE = \"schema.json\";\n\n/** Sanitise a user-supplied slug into a safe directory-name leaf.\n * Returns null for anything that fails the slug whitelist OR isn't a\n * basename (i.e. survives `path.basename` round-trip unchanged).\n * The basename round-trip is the pattern CodeQL recognises as a\n * `js/path-injection` sanitiser. */\nexport function safeSlugName(slug: string): string | null {\n if (typeof slug !== \"string\") return null;\n if (!SAFE_SLUG_PATTERN.test(slug)) return null;\n const basename = path.basename(slug);\n if (basename !== slug) return null;\n return basename;\n}\n\n/** Sanitise a user-supplied record id into a safe filename stem. Like\n * `safeSlugName` but tolerates interior dots (so natural keys work),\n * while still rejecting any `..` substring, path separators, and\n * leading/trailing dots. The `path.basename` round-trip is the same\n * `js/path-injection` sanitiser CodeQL recognises on `safeSlugName`. */\nexport function safeRecordId(recordId: string): string | null {\n if (typeof recordId !== \"string\") return null;\n if (!SAFE_RECORD_ID_PATTERN.test(recordId)) return null;\n if (recordId.includes(\"..\")) return null;\n const basename = path.basename(recordId);\n if (basename !== recordId) return null;\n return basename;\n}\n\n/** Realpath the closest existing ancestor of `absPath` and return it.\n * Returns null if no ancestor exists or if the realpath call fails\n * for a non-ENOENT reason (permissions, etc.). Used by\n * `containedPath` to defend against symlinks pointing outside the\n * workspace even when the leaf hasn't been created yet. */\nfunction realpathClosestAncestor(absPath: string): string | null {\n let cursor = absPath;\n while (cursor !== path.dirname(cursor)) {\n try {\n return realpathSync(cursor);\n } catch (err) {\n if (isErrorWithCode(err) && err.code === \"ENOENT\") {\n cursor = path.dirname(cursor);\n continue;\n }\n return null;\n }\n }\n return null;\n}\n\n/** True iff the realpath'd closest existing ancestor of `absPath`\n * resolves under `rootPath`'s realpath. Pure helper, takes both\n * paths explicitly so tests can drive it against a `mkdtempSync`\n * root without touching the user's workspace. Defends against the\n * data dir or any ancestor being a symlink to a directory outside\n * the workspace — lexical-only checks (`path.resolve` + prefix\n * match) would miss this case, which is the class of bug the rest\n * of this codebase uses realpath-based containment to avoid (see\n * `server/utils/files/safe.ts#resolveWithinRoot`). */\nexport function isContainedInRoot(absPath: string, rootPath: string): boolean {\n let rootReal: string;\n try {\n rootReal = realpathSync(rootPath);\n } catch {\n return false;\n }\n const ancestorReal = realpathClosestAncestor(absPath);\n if (ancestorReal === null) return false;\n if (ancestorReal === rootReal) return true;\n return ancestorReal.startsWith(rootReal + path.sep);\n}\n\n// NOTE: there is deliberately no `isContainedInWorkspace(absPath)` helper here.\n// It existed, had zero callers, and was exactly the shape a contributor reaches\n// for by reflex — at which point containment is checked against the AMBIENT\n// root while the operation it guards runs under an explicit one. Pass the root\n// you are operating on to `isContainedInRoot` instead.\n\n/** Resolve a schema-declared dataPath against `rootPath` (default:\n * the live workspace), refusing anything that escapes — absolute\n * paths, `..`-segments, empty string, or symlinks pointing outside\n * the root. Returns the absolute path on success, null on refusal.\n * Does NOT require the directory to exist; the caller may create it\n * on first write. The realpath containment check covers the symlink\n * case at discovery time; io operations re-check before each write\n * to defend against symlinks introduced between discovery and use.\n *\n * `rootPath` exists as an optional override so a test (or a tool\n * driving discovery against a `mkdtempSync` tree) gets a dataDir\n * rooted at the same place it asked to scan, not the real workspace.\n * Without this, `discoverApps({ workspaceRoot: tmpdir })` would\n * discover skills in tmpdir but resolve every app's dataDir against\n * `~/mulmoclaude/`, breaking isolation. */\nexport function resolveDataDir(dataPath: string, rootPath: string = getWorkspaceRoot()): string | null {\n if (typeof dataPath !== \"string\" || dataPath.length === 0) return null;\n if (path.isAbsolute(dataPath)) return null;\n const normalized = path.normalize(dataPath);\n if (normalized.startsWith(\"..\") || normalized.includes(`${path.sep}..${path.sep}`)) return null;\n const resolved = path.resolve(rootPath, normalized);\n if (!isContainedInRoot(resolved, rootPath)) return null;\n return resolved;\n}\n\n/** Compose the absolute path to a single record file. Both arguments\n * must have been passed through `safeSlugName` / `resolveDataDir`\n * before reaching here so the join can't escape. */\nexport function itemFilePath(dataDir: string, itemId: string): string {\n return path.join(dataDir, `${itemId}.json`);\n}\n\n/** Resolve an action's skill-relative `template` path against\n * `skillDir`, refusing escapes — absolute paths, `..`-segments, or a\n * symlink pointing outside the skill dir. Mirrors `resolveDataDir`;\n * the realpath containment is the hard guarantee. Returns the\n * absolute path on success, null on refusal. */\nexport function resolveTemplatePath(skillDir: string, templateRelPath: string): string | null {\n if (typeof templateRelPath !== \"string\" || templateRelPath.length === 0) return null;\n if (path.isAbsolute(templateRelPath)) return null;\n const normalized = path.normalize(templateRelPath);\n if (normalized.startsWith(\"..\") || normalized.includes(`${path.sep}..${path.sep}`)) return null;\n const resolved = path.resolve(skillDir, normalized);\n if (!isContainedInRoot(resolved, skillDir)) return null;\n return resolved;\n}\n","// Pure paging/projection primitives shared by the store implementations.\n// Split out of store.ts so a backend module (e.g. sqliteStore.ts, which\n// store.ts imports to register its factory) can use them without a\n// runtime import cycle. store.ts re-exports everything here, so the\n// public `@mulmoclaude/core/collection/server` surface is unchanged.\n\nimport type { CollectionItem } from \"../core/schema\";\nimport { projectRecordFields } from \"../core/project\";\n\n/** Options for `page`. STORED fields only — computed fields (`derived` /\n * `toggle` / `embed` / rollups) never reach the store; project them at the\n * engine level after enrichment. */\nexport interface ListOptions {\n /** 0-based offset into the store's stable order (see the contract). */\n offset?: number;\n /** Max records returned. Absent = all remaining (subject to store caps). */\n limit?: number;\n /** Keep only these fields per record (the primary key is always kept). */\n fields?: readonly string[];\n}\n\nexport interface ListPage {\n items: CollectionItem[];\n /** Records in the collection before offset/limit — a lower bound when\n * `truncated` (the store capped its scan, e.g. `MAX_CSV_ROWS`). */\n total: number;\n truncated: boolean;\n}\n\nexport interface WriteOptions {\n /** Create semantics: fail with `kind: \"conflict\"` when the record\n * already exists (an O_EXCL open in the file store — race-safe). */\n refuseOverwrite?: boolean;\n}\n\n/** Project `fields` (+ the primary key, always) out of each record. Thin\n * server-typed alias over the shared isomorphic `projectRecordFields`\n * (../core/project.ts) — kept as the store layer's exported name. */\nexport function projectItemFields(items: CollectionItem[], fields: readonly string[] | undefined, primaryKey: string): CollectionItem[] {\n return projectRecordFields(items, fields, primaryKey);\n}\n\n/** Slice + project an already-ordered full read into a `ListPage` — the\n * shared emulation for stores without native paging. Pure, exported for\n * tests. `limit: 0` is a valid \"count only\" page. */\nexport function pageFromFullRead(items: CollectionItem[], opts: ListOptions, primaryKey: string, truncated: boolean): ListPage {\n const offset = Math.max(0, opts.offset ?? 0);\n const end = opts.limit === undefined ? items.length : offset + Math.max(0, opts.limit);\n return { items: projectItemFields(items.slice(offset, end), opts.fields, primaryKey), total: items.length, truncated };\n}\n","// The Firestore store: a SHARED collection's records as Firestore documents.\n//\n// Documents live at `apps/{aid}/collections/{cid}/items/{id}`. The identity is\n// `(aid, cid)`: `aid` comes from the repository's committed `app.json` and is\n// resolved once by discovery (`LoadedCollection.appId`), `cid` is always the\n// collection's slug. Nothing here reads a file or a session to work out WHERE —\n// it is handed a settled identity and builds the path from it.\n//\n// WHAT PROTECTS THESE DOCUMENTS. Not the shape of the path. An earlier draft of\n// this backend wrote under `users/{uid}/…` and leaned on the deployed rule\n// `users/{uid}/{document=**}`, so \"the schema cannot name a path\" WAS the\n// safety argument. It no longer is, and reading it that way would be a lie: an\n// `aid` is committed in a repository that anyone with a clone can edit. What\n// authorizes a read or a write is the app's MEMBER ROSTER — the rules resolve\n// `request.auth.token.email` against `apps/{aid}.members` and derive a role per\n// collection. Pointing at another app's `aid` is not an escape; it is a request\n// that gets refused, by name, for a caller who is not on that roster.\n//\n// The schema still declares no path, but for a different reason: there is\n// nothing for it to say. `aid` is one per app (four collections share one\n// roster), and `cid` is the slug. See `StorageZ`'s firestore arm.\n//\n// Availability: the authenticated handle belongs to the host's remote-host\n// session, so a shared collection is readable/writable only while that session\n// is open. This follows sqliteStore's precedent for an unavailable engine — the\n// FACTORY never throws (`storeFor` is called from ontology/validate/routes and\n// must not break unrelated screens), each METHOD fails with an actionable\n// message instead. It must never degrade to an empty result: \"no records\" and\n// \"not connected\" have to stay distinguishable, or a disconnected session looks\n// like data loss.\n//\n// SDK access goes through the `FirestoreDocs` seam (firestoreDocs.ts), not the\n// modular functions directly — that is what makes the backend testable without\n// a live Firestore.\n//\n// No `query`: there is no Firestore analogue of the DuckDB aggregation the CSV\n// store exposes. Absent `query` is a supported state — the engine-level\n// fallback (`runCollectionQuery`) answers aggregations instead.\n\nimport { isRecord } from \"@mulmoclaude/common\";\nimport { sharedCollectionKey, type SharedCollectionKey } from \"../core/collectionKey\";\nimport type { CollectionItem } from \"../core/schema\";\nimport { BackendUnavailableError } from \"./backendAvailability\";\nimport type { LoadedCollection } from \"./discoveredCollection\";\nimport { backoffDelayMs, classifyListenerError } from \"../../firestore/listen\";\nimport { firestoreHandle, log, publishCollectionChange, sharedCollectionChangePayload, type FirestoreHandle } from \"./host\";\nimport type { DeleteItemResult, IoOptions, WriteItemResult } from \"./io\";\nimport { safeRecordId } from \"./paths\";\nimport { projectItemFields, type ListOptions, type ListPage, type WriteOptions } from \"./storePage\";\nimport type { CollectionStore, StoreChangeListener, StoreUnsubscribe } from \"./store\";\n\n/** What every operation throws when there is no live session. Worded as an\n * instruction because it surfaces straight to the user and the agent. */\nconst NOT_CONNECTED =\n \"shared collection unavailable: connect remote-host first — these records live in the app's Firestore, not in the workspace, so nothing can be read or written while the session is closed\";\n\n/** What a schema declaring `storage.type: \"firestore\"` must have had resolved\n * for it before it can be served. Its absence is a programming error here, not\n * a user-facing state: discovery REFUSES such a schema when the repository\n * declares no `aid`, so a collection that reached this store has one. */\nconst NO_APP = \"shared collection has no app id — discovery should have refused this schema; check that the repository's app.json declares an `aid`\";\n\n/** The records subcollection of one shared collection.\n *\n * Takes a KEY, never loose strings, and the key is the only way to reach this\n * function. `sharedCollectionKey` is where the name rule lives (the charset a\n * Firestore document id, a pubsub channel segment and the completion-bell id\n * must all survive), so building a path cannot be a way around it. */\nexport function sharedItemsPath(key: SharedCollectionKey): string {\n return `apps/${key.aid}/collections/${key.cid}/items`;\n}\n\n/** The collection's identity, from what discovery resolved. Throws on a\n * missing `appId` — see NO_APP. */\nfunction keyOf(collection: Pick<LoadedCollection, \"slug\" | \"appId\">): SharedCollectionKey {\n if (collection.appId === undefined) throw new Error(NO_APP);\n // `cid` IS the slug. Fixed here, deliberately, rather than made configurable:\n // the schema, the views and the skill text sit in a directory named by the\n // slug, so a second name would need a mapping table between what a collection\n // is called on disk and what it is called in its app — two names for one\n // thing, which is the exact collision `CollectionKey` exists to remove.\n return sharedCollectionKey(collection.appId, collection.slug);\n}\n\nfunction requireHandle(): FirestoreHandle {\n const handle = firestoreHandle();\n if (handle === null) throw new BackendUnavailableError(NOT_CONNECTED);\n return handle;\n}\n\n/** Firestore's own refusal, named.\n *\n * `permission-denied` is the failure a shared collection has most often and\n * the one the SDK explains worst (\"Missing or insufficient permissions\") — it\n * says nothing about WHO was refused, which is the only fact that leads to a\n * fix. Authorization here is the app's member roster, keyed by email, so the\n * signed-in address is what the app's owner needs in order to add it. This is\n * the whole reason `FirestoreHandle` carries `email`.\n *\n * Reported as a `BackendUnavailableError` deliberately, even though it is a\n * refusal rather than an outage: the layers above catch broadly, and without a\n * type to test, `store.read(...).catch(() => null)` reports \"record missing\"\n * and an ontology count reports 0 — a denial would read as an empty\n * collection, which is the exact confusion this backend refuses to create. */\nfunction isPermissionDenied(err: unknown): boolean {\n return isRecord(err) && err.code === \"permission-denied\";\n}\n\nfunction deniedMessage(key: SharedCollectionKey, email: string): string {\n return `permission denied on shared collection '${key.cid}' of app '${key.aid}' — signed in as ${email}. A shared collection is authorized by the app's member roster (by email), so this address needs a role for '${key.cid}' (or '*'); only the app's owner can add it.`;\n}\n\n/** Run one SDK call, translating a roster denial. Every read and write goes\n * through this — a denial reaching one path and not another would mean the\n * message a user sees depends on which screen they were on. */\nasync function guarded<T>(key: SharedCollectionKey, email: string, run: () => Promise<T>): Promise<T> {\n try {\n return await run();\n } catch (err) {\n if (!isPermissionDenied(err)) throw err;\n throw new BackendUnavailableError(deniedMessage(key, email));\n }\n}\n\n/** A stored document's fields → a record. A document written by hand (or by an\n * older version) can hold anything, so a non-object is dropped rather than\n * surfaced as a broken record — the same fail-soft the file store applies to\n * an unparseable `.json`. */\n/** A stored document as a record — with its identity taken from the DOCUMENT\n * ID, never from the document's own fields.\n *\n * This is the one place a shared record's identity is decided, and it is\n * decided against the field the writer supplied on purpose.\n *\n * The rules can constrain the document id (`idFrom` pins it to the\n * submitter's uid, or to uid+field) and they CANNOT constrain the value of a\n * field: `validateOk` checks which keys are present, `keyFieldsOk` checks a\n * declared enum, and nothing compares `request.resource.data[primaryKey]`\n * with the path being written. So a public submitter writing at their one\n * permitted document id could put ANY value in the primary-key field —\n * another member's record id, or a duplicate — and every reader would take it\n * as the record's identity. Overwriting it here makes that unreachable rather\n * than merely discouraged, and it costs nothing: for a record written through\n * this store the two already agree, because `firestoreWrite` writes at the id\n * it was given.\n *\n * It also removes the reason a submit path would have had to accept the\n * primary key as a `createField` at all — a submission that cannot name its\n * own id is exactly right when the id is the thing being assigned. */\nfunction toItem(data: unknown, docId: string, primaryKey: string): CollectionItem | null {\n return isRecord(data) ? { ...data, [primaryKey]: docId } : null;\n}\n\n/** Record ids are validated with the SAME helper every other backend uses.\n * Firestore would accept ids the file store refuses, but a record should stay\n * portable between backends — and an id that can't round-trip to a filename\n * would break an export back to a file collection. */\nfunction withSafeId<T>(itemId: string, onInvalid: () => T, run: (safeId: string, handle: FirestoreHandle) => T): T {\n const safeId = safeRecordId(itemId);\n if (safeId === null) return onInvalid();\n return run(safeId, requireHandle());\n}\n\nasync function firestoreList(key: SharedCollectionKey, primaryKey: string): Promise<CollectionItem[]> {\n const { docs, email } = requireHandle();\n const entries = await guarded(key, email, () => docs.list(sharedItemsPath(key)));\n return entries.map((entry) => toItem(entry.data, entry.id, primaryKey)).filter((item): item is CollectionItem => item !== null);\n}\n\n/** Paging is emulated over a full ordered read rather than pushed into\n * Firestore: `offset` has no server-side form there (the cursor API needs the\n * preceding document, which a stateless offset/limit call doesn't have), and\n * `total` needs the full count anyway. Hence `nativePaging: false` — the\n * capability is honest about the cost. */\nasync function firestorePage(key: SharedCollectionKey, primaryKey: string, opts: ListOptions): Promise<ListPage> {\n const items = await firestoreList(key, primaryKey);\n const offset = Math.max(0, opts.offset ?? 0);\n const sliced = opts.limit === undefined ? items.slice(offset) : items.slice(offset, offset + Math.max(0, opts.limit));\n return { items: projectItemFields(sliced, opts.fields, primaryKey), total: items.length, truncated: false };\n}\n\nasync function firestoreRead(key: SharedCollectionKey, itemId: string, primaryKey: string): Promise<CollectionItem | null> {\n return withSafeId(\n itemId,\n () => Promise.resolve(null),\n async (safeId, { docs, email }) => toItem(await guarded(key, email, () => docs.get(sharedItemsPath(key), safeId)), safeId, primaryKey),\n );\n}\n\n/** Publish the \"records changed\" ping for a shared collection.\n *\n * `sharedCollectionChangePayload` NEVER stamps a root, and that matters beyond\n * tidiness: this payload is relayed to the browser and on into an\n * LLM-generated custom-view iframe, so a filesystem path on it would be a\n * disclosure. The type makes it unreachable rather than trusting the caller. */\nfunction publishShared(key: SharedCollectionKey, ids: string[], operation: \"upsert\" | \"delete\"): void {\n publishCollectionChange(sharedCollectionChangePayload({ slug: key.cid, ids, op: operation }, key.aid));\n}\n\nasync function firestoreWrite(\n key: SharedCollectionKey,\n itemId: string,\n item: CollectionItem,\n opts: IoOptions & { refuseOverwrite?: boolean | undefined },\n): Promise<WriteItemResult> {\n return withSafeId<Promise<WriteItemResult>>(\n itemId,\n () => Promise.resolve({ kind: \"invalid-id\", itemId }),\n async (safeId, { docs, email }) => {\n const collectionPath = sharedItemsPath(key);\n if (opts.refuseOverwrite) {\n const created = await guarded(key, email, () => docs.create(collectionPath, safeId, item));\n if (!created) return { kind: \"conflict\", itemId: safeId };\n } else {\n await guarded(key, email, () => docs.set(collectionPath, safeId, item));\n }\n if (opts.slug) publishShared(key, [safeId], \"upsert\");\n return { kind: \"ok\", itemId: safeId, item };\n },\n );\n}\n\nasync function firestoreDelete(key: SharedCollectionKey, itemId: string, opts: IoOptions): Promise<DeleteItemResult> {\n return withSafeId<Promise<DeleteItemResult>>(\n itemId,\n () => Promise.resolve({ kind: \"invalid-id\", itemId }),\n async (safeId, { docs, email }) => {\n const removed = await guarded(key, email, () => docs.delete(sharedItemsPath(key), safeId));\n if (!removed) return { kind: \"not-found\", itemId: safeId };\n if (opts.slug) publishShared(key, [safeId], \"delete\");\n return { kind: \"ok\", itemId: safeId };\n },\n );\n}\n\n// --- live updates -----------------------------------------------------------\n\n/** One live subscription to a shared collection's records. */\ninterface SharedWatch {\n key: SharedCollectionKey;\n onChange: StoreChangeListener;\n stopped: boolean;\n detach: () => void;\n retryTimer: ReturnType<typeof setTimeout> | null;\n attempt: number;\n}\n\n/** Deliver one snapshot's changes.\n *\n * THE FIRST SNAPSHOT IS ONE COLLECTION-LEVEL REPORT, not N per-record ones.\n * `onSnapshot` hands over the collection's current contents immediately, as a\n * snapshot in which every existing document reads as `added`.\n *\n * Dropping it is wrong. There is a GAP on either side of a subscription — the\n * boot reconcile finishes before the listener arms, and a listener that died\n * is re-armed after a backoff — and a record that moved inside one of those\n * gaps appears only in that first snapshot. Since this backend now HAS a\n * `watch`, the watcher's periodic re-derivation no longer covers it, so a\n * dropped first snapshot means stale bells and stale views until that record\n * happens to change again. That is exactly the failure this step was supposed\n * to remove.\n *\n * Announcing every record individually is also wrong: it is a refresh storm\n * to every open view, on every mount and every reconnect. `{ kind: \"collection\" }`\n * says the same thing in one event — the watcher answers it with a full\n * re-derivation plus a sweep, which is precisely \"work out what changed while\n * I was not listening\".\n *\n * A change this process made itself also arrives here — the write path has\n * already published for it, so the record is reconciled twice. Harmless\n * (reconciling is idempotent) and left alone: suppressing it would mean\n * tracking our own in-flight writes, and getting that wrong loses a real\n * change rather than a duplicate one. */\nfunction deliverSnapshot(run: SharedWatch, ids: string[], initial: boolean): void {\n if (run.stopped) return;\n if (initial) {\n run.onChange({ kind: \"collection\" });\n return;\n }\n for (const itemId of ids) run.onChange({ kind: \"item\", itemId });\n}\n\n/** A listener died. Firestore never revives one on its own, so the choice is\n * re-subscribe or go dark.\n *\n * NO OVERALL RETRY WINDOW, unlike `hostRunner`'s listener. There, giving up\n * escalates to a lifecycle owner that can re-authenticate; here there is\n * nobody above to escalate to — the watcher registers a mounted collection\n * once and never re-arms it, so \"give up\" means this collection serves stale\n * data until the server restarts. A capped 30s re-subscribe is cheap and\n * makes recovery automatic when the session comes back.\n *\n * A FATAL error still stops: `permission-denied` is what a revoked membership\n * looks like, and re-listening cannot restore a grant. It is logged as such\n * rather than retried in silence. */\nfunction handleWatchError(run: SharedWatch, error: unknown): void {\n if (run.stopped) return;\n if (classifyListenerError(error) === \"fatal\") {\n log.warn(\"collections\", \"shared collection listener stopped\", {\n aid: run.key.aid,\n cid: run.key.cid,\n error: isRecord(error) && typeof error.message === \"string\" ? error.message : String(error),\n detail: \"live updates for this collection are off until the server re-syncs; a revoked membership looks exactly like this\",\n });\n return;\n }\n run.retryTimer = setTimeout(() => subscribeShared(run), backoffDelayMs(run.attempt));\n run.attempt += 1;\n}\n\n/** (Re-)arm the listener. The handle is fetched HERE rather than captured: a\n * retry may land after the session was replaced, and listening through a\n * closed session's handle would fail forever. */\nfunction subscribeShared(run: SharedWatch): void {\n run.retryTimer = null;\n if (run.stopped) return;\n const handle = firestoreHandle();\n if (handle === null) {\n // Not connected yet. The steady state while remote-host is closed, so it\n // is a wait rather than an error — no log, capped backoff, and the next\n // attempt picks up the session the moment it opens.\n run.retryTimer = setTimeout(() => subscribeShared(run), backoffDelayMs(run.attempt));\n run.attempt += 1;\n return;\n }\n run.detach = handle.docs.watch(\n sharedItemsPath(run.key),\n (ids, meta) => {\n // A healthy snapshot proves the listener recovered: the ladder starts\n // fresh for whatever comes next.\n run.attempt = 0;\n deliverSnapshot(run, ids, meta.initial);\n },\n (error) => handleWatchError(run, error),\n );\n}\n\n/** Subscribe to a shared collection's records.\n *\n * Returns `null` when there is no session: the watcher reads that as \"has a\n * watch but could not arm it\", leaves the collection unmounted and retries on\n * the next sync tick — which is the right channel for a connection that has\n * not happened yet. Arming a dead listener instead would mark the slug mounted\n * forever. */\nfunction armSharedWatch(key: SharedCollectionKey, onChange: StoreChangeListener): StoreUnsubscribe | null {\n if (firestoreHandle() === null) return null;\n const run: SharedWatch = { key, onChange, stopped: false, detach: () => {}, retryTimer: null, attempt: 0 };\n subscribeShared(run);\n return () => {\n run.stopped = true;\n if (run.retryTimer !== null) clearTimeout(run.retryTimer);\n run.detach();\n };\n}\n\n/** The store factory registered for `storage.type === \"firestore\"`.\n * Synchronous and connection-agnostic by contract — see the header. */\nexport function firestoreStoreFor(collection: LoadedCollection, opts: IoOptions): CollectionStore {\n const { primaryKey } = collection.schema;\n const ioOpts: IoOptions = { ...opts, slug: opts.slug ?? collection.slug };\n // Every method is `async` and resolves the key INSIDE itself, so a bad\n // identity rejects the one call instead of throwing out of the factory. The\n // factory is called from ontology / validate / route handlers that list many\n // collections; one that throws there takes an unrelated screen down with it.\n return {\n capabilities: { writable: true, nativeQuery: false, nativePaging: false },\n list: async () => firestoreList(keyOf(collection), primaryKey),\n page: async (pageOpts = {}) => firestorePage(keyOf(collection), primaryKey, pageOpts),\n read: async (itemId: string) => firestoreRead(keyOf(collection), itemId, primaryKey),\n write: async (itemId: string, item: CollectionItem, writeOpts: WriteOptions = {}) =>\n firestoreWrite(keyOf(collection), itemId, item, { ...ioOpts, refuseOverwrite: writeOpts.refuseOverwrite }),\n delete: async (itemId: string) => firestoreDelete(keyOf(collection), itemId, ioOpts),\n // Having a `watch` at all is what takes this backend out of the watcher's\n // clock-tick fallback: `cannotReportChanges()` asks the store whether it\n // can report, never which backend it is, so this one line is the whole\n // change on that side.\n watch: async (onChange) => armSharedWatch(keyOf(collection), onChange),\n };\n}\n","// The repository's app declaration — `<root>/app.json` — and the one field\n// this step reads from it: `aid`.\n//\n// WHY THE REPOSITORY AND NOT A BINDING. A shared collection's identity is\n// `(aid, cid)`, and `aid` is COMMITTED, so every clone of the repository\n// resolves the same app and an invitation is about authorization rather than\n// discovery. That makes `aid` a property of the collection's LOCATION, exactly\n// like `storage.path` — not a property of the session. A host binding would\n// make it a process global, which is wrong the moment one server process serves\n// several project roots (MulmoTerminal does), because then every repository's\n// collections would point at one app.\n//\n// WHY NOT THE SCHEMA. The unit of sharing is the app, not the collection: four\n// collections share one member roster and one public config, so `aid` sits once\n// per repository rather than once per schema. A per-collection `aid` would also\n// be a second place to change it, and the two would drift.\n//\n// AUTHORED, NOT PUBLISHED. This is the file a human (or the agent) writes. The\n// Firestore document at `apps/{aid}` is a DIFFERENT thing that `publish`\n// derives from it — flattened for the security rules to read, with epoch-millis\n// windows and a derived `memberEmails`. Do not read this file as if it were\n// that one, and do not write this file from anything that publishes.\n//\n// SCOPE. Only `aid` is read here. `members` / `public` belong to `publish`,\n// which does not exist yet. `aidEnv` (a per-worktree app id, so a feature\n// branch cannot mutate the team's live records) also does not: it needs the\n// host to resolve a worktree variable that lives in MulmoTerminal's session\n// environment and NOT in this process's `process.env`, so it arrives as a host\n// resolver hook rather than a direct env read. Both land later; the point of\n// funnelling every read through this one function is that they land HERE and\n// nowhere else.\n\nimport { readFileSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { isErrorWithCode, isRecord } from \"@mulmoclaude/common\";\nimport { isValidCollectionName } from \"../core/collectionKey\";\n\n/** The app declaration's filename, at the repository root. */\nexport const APP_MANIFEST_FILE = \"app.json\";\n\n/** What this step reads out of `app.json`. Deliberately one field: every key\n * added here is a key `publish` and this loader could disagree about. */\nexport interface AppManifest {\n /** The app id — the `{aid}` in `apps/{aid}/collections/{cid}/items`. */\n aid: string;\n}\n\n/** Why a root has no usable `aid`. Returned rather than thrown because the\n * caller is an acceptance gate whose whole job is to turn this into a\n * one-line reason a collection was skipped. */\nexport type AppManifestFailure = { kind: \"missing\" } | { kind: \"unreadable\"; detail: string } | { kind: \"malformed\"; detail: string };\n\nexport type AppManifestResult = { ok: true; manifest: AppManifest } | ({ ok: false } & AppManifestFailure);\n\n/** Read `<root>/app.json` and return its `aid`.\n *\n * SYNCHRONOUS on purpose. The caller is `acceptParsedSchema`, which is sync\n * and is shared by discovery and `manageCollection`'s `putSchema` precisely so\n * that a schema which would be skipped on the next discovery cannot be written\n * as if it were fine. Making this async would split that gate in two, and the\n * half that lost the check is the half the author sees. The file is a few\n * hundred bytes and is read once per firestore collection per discovery pass.\n *\n * Never cached. `app.json` is edited by hand and by the agent, and a cache\n * here would mean the app a collection points at is whatever it was when the\n * server started. */\nexport function loadAppManifest(root: string): AppManifestResult {\n let raw: string;\n try {\n raw = readFileSync(path.join(root, APP_MANIFEST_FILE), \"utf-8\");\n } catch (err) {\n if (isErrorWithCode(err) && err.code === \"ENOENT\") return { ok: false, kind: \"missing\" };\n return { ok: false, kind: \"unreadable\", detail: String(err) };\n }\n return parseAppManifest(raw);\n}\n\n/** The parse half, exported so it can be tested without a filesystem.\n *\n * `aid` is validated with `isValidCollectionName` — the SAME predicate the\n * `CollectionKey` constructors apply — rather than a rule of its own. An `aid`\n * is re-encoded downstream as a Firestore document id, a pubsub channel\n * segment and a cache key, each with a different character that would break\n * it; one rule, stated once, is what keeps those layers from disagreeing.\n * Rejecting here rather than at `sharedCollectionKey` only changes WHERE the\n * author is told: a reason on the collection they wrote, instead of a throw\n * from inside a store call. */\nexport function parseAppManifest(raw: string): AppManifestResult {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n return { ok: false, kind: \"malformed\", detail: `not valid JSON (${String(err)})` };\n }\n if (!isRecord(parsed)) return { ok: false, kind: \"malformed\", detail: \"is not a JSON object\" };\n const { aid } = parsed;\n if (typeof aid !== \"string\" || aid.length === 0) return { ok: false, kind: \"malformed\", detail: \"declares no `aid` string\" };\n if (!isValidCollectionName(aid)) return { ok: false, kind: \"malformed\", detail: `\\`aid\\` '${aid}' is not a valid app id` };\n return { ok: true, manifest: { aid } };\n}\n\n/** The failure as the one line an author can act on. Kept next to the failure\n * type so a new variant cannot be added without wording it. */\nexport function appManifestReason(failure: AppManifestFailure, root: string): string {\n const manifestPath = path.join(root, APP_MANIFEST_FILE);\n if (failure.kind === \"missing\") return `a shared collection needs an app: create ${manifestPath} declaring an \\`aid\\``;\n if (failure.kind === \"unreadable\") return `cannot read ${manifestPath}: ${failure.detail}`;\n return `${manifestPath} ${failure.detail}`;\n}\n","// Read / write item files for schema-driven collections. Records live at\n// `<dataDir>/<itemId>.json`, one JSON object per file. Writes are\n// atomic; deletes are idempotent enough to expose a clear 404 when\n// the file is missing.\n\nimport { lstat, mkdir, open, readdir, readFile, unlink } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { collectionChangePayload, getWorkspaceRoot, log, publishCollectionChange } from \"./host\";\nimport { writeFileAtomic } from \"../../files/atomic.js\";\nimport { newItemId } from \"../core/itemId\";\nimport { isContainedInRoot, itemFilePath, safeRecordId } from \"./paths\";\nimport type { CollectionItem, CollectionSchema } from \"../core/schema\";\nimport { isErrorWithCode, isRecord } from \"@mulmoclaude/common\";\n\nexport interface IoOptions {\n /** Override the workspace root for containment checks. Default:\n * the live `workspacePath`. Tests point this at a `mkdtempSync`\n * tree so the realpath-based escape detection can be exercised\n * without touching `~/mulmoclaude/`. Same pattern as\n * `server/workspace/skills/catalog.ts#CatalogOptions`. */\n workspaceRoot?: string | undefined;\n /** Collection slug this write/delete belongs to. When provided, a\n * successful write/delete publishes a record-change event (see\n * `publishCollectionChange`) so live views refetch. `writeItem` has no\n * slug of its own (it's keyed by `dataDir`), so callers thread it through;\n * omitting it just means no event is published (internal / test writes). */\n slug?: string | undefined;\n}\n\n/** True iff `filePath` exists and is a regular file (NOT a symlink).\n * Defends `listItems` / `readItem` against `*.json` symlinks placed\n * inside an otherwise-contained data dir — without this, a record\n * file could symlink to /etc/passwd and the detail endpoint would\n * happily serve it. Returns false on ENOENT and on any other lstat\n * failure so the caller's \"missing\" branch covers those cases too.\n * Exported so `ontology.ts`'s record COUNT classifies entries with the\n * SAME lstat logic — the two must agree on what a record file is. */\nexport async function isRegularFile(filePath: string): Promise<boolean> {\n try {\n const info = await lstat(filePath);\n return info.isFile();\n } catch {\n return false;\n }\n}\n\n/** Read one JSON record file. Returns null when the file is missing,\n * is a symlink (file-disclosure defense), parses to a non-object,\n * or has a read/parse error. Caller logs the per-entry skip — this\n * helper just classifies. Split out to keep `listItems` under the\n * `sonarjs/cognitive-complexity` threshold. */\n/** Parse a record file's text into a plain-object `CollectionItem`, or\n * null when it isn't a JSON object (array / scalar / null). */\nfunction parseRecordJson(raw: string): CollectionItem | null {\n const parsed: unknown = JSON.parse(raw);\n return isRecord(parsed) ? parsed : null;\n}\n\nasync function tryReadRecord(filePath: string): Promise<CollectionItem | null> {\n if (!(await isRegularFile(filePath))) return null;\n try {\n return parseRecordJson(await readFile(filePath, \"utf-8\"));\n } catch {\n return null;\n }\n}\n\n/** Read every record under `dataDir`. Returns [] if the dir doesn't\n * exist yet (legitimate first-use state). Malformed JSON files and\n * symlinked records are skipped (the latter is a file-disclosure\n * defense — see `isRegularFile`). Re-validates the realpath\n * containment to defend against a symlinked data dir appearing\n * between discovery and use. */\nexport async function listItems(dataDir: string, opts: IoOptions = {}): Promise<CollectionItem[]> {\n const workspaceRoot = opts.workspaceRoot ?? getWorkspaceRoot();\n if (!isContainedInRoot(dataDir, workspaceRoot)) {\n log.warn(\"collections\", \"listItems refused: dataDir escapes workspace via symlink\", { dataDir });\n return [];\n }\n let entries: string[];\n try {\n entries = await readdir(dataDir);\n } catch (err) {\n if (isErrorWithCode(err) && err.code === \"ENOENT\") return [];\n throw err;\n }\n const results: CollectionItem[] = [];\n for (const name of entries) {\n if (!name.endsWith(\".json\")) continue;\n if (name.startsWith(\".\")) continue;\n const filePath = path.join(dataDir, name);\n const record = await tryReadRecord(filePath);\n if (record === null) {\n log.warn(\"collections\", \"skipping record (missing, symlink, or unreadable)\", { path: filePath });\n continue;\n }\n results.push(record);\n }\n return results;\n}\n\n/** Read one record by id. Returns null when the file is missing,\n * when the resolved path escapes the workspace via a symlink, or\n * when the record file itself is a symlink (file-disclosure\n * defense — see `isRegularFile`). */\nexport async function readItem(dataDir: string, itemId: string, opts: IoOptions = {}): Promise<CollectionItem | null> {\n const safeId = safeRecordId(itemId);\n if (safeId === null) return null;\n const workspaceRoot = opts.workspaceRoot ?? getWorkspaceRoot();\n if (!isContainedInRoot(dataDir, workspaceRoot)) return null;\n const filePath = itemFilePath(dataDir, safeId);\n if (!(await isRegularFile(filePath))) return null;\n try {\n return parseRecordJson(await readFile(filePath, \"utf-8\"));\n } catch (err) {\n if (isErrorWithCode(err) && err.code === \"ENOENT\") return null;\n throw err;\n }\n}\n\nexport interface WriteItemOptions extends IoOptions {\n /** When true (POST/create), refuse to overwrite an existing file\n * and return `kind: \"conflict\"`. Update flow (PUT) leaves it false. */\n refuseOverwrite?: boolean | undefined;\n}\n\nexport type WriteItemResult =\n | { kind: \"ok\"; itemId: string; item: CollectionItem }\n | { kind: \"invalid-id\"; itemId: string }\n | { kind: \"conflict\"; itemId: string }\n | { kind: \"path-escape\"; itemId: string };\n\n/** The symlink-containment refusal every record path shares: one check, one\n * warn, one answer. Extracted because this is a security RULE applied at\n * three sites (write pre-mkdir, write post-mkdir, delete) — a fix to the\n * check must not be able to land at only one of them.\n *\n * `stage` names the call site so the warn stays as diagnosable as the three\n * hand-written copies were.\n *\n * Scope, stated explicitly because a reviewer asks every time: this catches\n * a symlink that EXISTS when we look — `isContainedInRoot` realpaths the\n * closest existing ancestor, so a pre-planted escape is refused. It does not\n * and cannot close the check-then-use race, where an ancestor is swapped for\n * a symlink between this call and the `mkdir` / `open` / `unlink` that\n * follows. Closing that needs directory-handle I/O anchored at the workspace\n * (`openat` + `O_NOFOLLOW`), which `node:fs` does not expose — it would mean\n * a different I/O layer, not a tighter check here.\n *\n * That race is deliberately outside this app's threat model: the process is\n * loopback-bound and bearer-authed, so anyone able to swap directories inside\n * the workspace is already the workspace owner — the same trust principal the\n * writes belong to. Revisit if collections ever serve a lower-trust caller. */\nfunction escapesWorkspace(dataDir: string, workspaceRoot: string, itemId: string, stage: string): boolean {\n if (isContainedInRoot(dataDir, workspaceRoot)) return false;\n log.warn(\"collections\", `${stage} refused: dataDir escapes workspace via symlink`, { dataDir, itemId });\n return true;\n}\n\n/** Write a record. Ensures the directory exists, validates the id,\n * re-checks symlink containment after mkdir, and writes atomically.\n *\n * Create path (`refuseOverwrite: true`) uses an O_EXCL `wx` open\n * rather than `stat` + `writeFileAtomic` to close a check-then-write\n * race: two concurrent POSTs would otherwise both pass the existence\n * check and one would silently overwrite the other. The trade-off\n * is that the create path is not crash-atomic (a partial file could\n * remain if the process dies mid-write); acceptable here because\n * records are small JSON blobs and the next read either parses or\n * is skipped via the \"malformed JSON\" branch in `listItems`.\n *\n * Update path (`refuseOverwrite: false`) uses `writeFileAtomic` so\n * PUT remains crash-atomic. No race there — the URL pins the id. */\nexport async function writeItem(dataDir: string, itemId: string, item: CollectionItem, opts: WriteItemOptions = {}): Promise<WriteItemResult> {\n const safeId = safeRecordId(itemId);\n if (safeId === null) return { kind: \"invalid-id\", itemId };\n const workspaceRoot = opts.workspaceRoot ?? getWorkspaceRoot();\n // Containment check runs BEFORE mkdir so we never create\n // directories outside the workspace even if a symlink ancestor\n // was swapped after discovery. We re-check AFTER mkdir to catch\n // a symlink racing in between the two — belt + suspenders, cheap\n // and the only honest defense against TOCTOU on directory creation.\n if (escapesWorkspace(dataDir, workspaceRoot, safeId, \"writeItem (pre-mkdir)\")) return { kind: \"path-escape\", itemId: safeId };\n await mkdir(dataDir, { recursive: true });\n if (escapesWorkspace(dataDir, workspaceRoot, safeId, \"writeItem (post-mkdir)\")) return { kind: \"path-escape\", itemId: safeId };\n const filePath = itemFilePath(dataDir, safeId);\n const payload = `${JSON.stringify(item, null, 2)}\\n`;\n\n if (opts.refuseOverwrite) {\n let handle;\n try {\n handle = await open(filePath, \"wx\");\n } catch (err) {\n if (isErrorWithCode(err) && err.code === \"EEXIST\") return { kind: \"conflict\", itemId: safeId };\n throw err;\n }\n try {\n await handle.writeFile(payload);\n } finally {\n await handle.close();\n }\n } else {\n await writeFileAtomic(filePath, payload);\n }\n // Publish AFTER the write lands so a live subscriber that refetches always\n // sees the new record (never a read-before-write race).\n if (opts.slug) publishCollectionChange(collectionChangePayload({ slug: opts.slug, ids: [safeId], op: \"upsert\" }, opts.workspaceRoot));\n return { kind: \"ok\", itemId: safeId, item };\n}\n\nexport type DeleteItemResult =\n { kind: \"ok\"; itemId: string } | { kind: \"invalid-id\"; itemId: string } | { kind: \"not-found\"; itemId: string } | { kind: \"path-escape\"; itemId: string };\n\nexport async function deleteItem(dataDir: string, itemId: string, opts: IoOptions = {}): Promise<DeleteItemResult> {\n const safeId = safeRecordId(itemId);\n if (safeId === null) return { kind: \"invalid-id\", itemId };\n const workspaceRoot = opts.workspaceRoot ?? getWorkspaceRoot();\n if (escapesWorkspace(dataDir, workspaceRoot, safeId, \"deleteItem\")) return { kind: \"path-escape\", itemId: safeId };\n const filePath = itemFilePath(dataDir, safeId);\n try {\n await unlink(filePath);\n if (opts.slug) publishCollectionChange(collectionChangePayload({ slug: opts.slug, ids: [safeId], op: \"delete\" }, opts.workspaceRoot));\n return { kind: \"ok\", itemId: safeId };\n } catch (err) {\n if (isErrorWithCode(err) && err.code === \"ENOENT\") return { kind: \"not-found\", itemId: safeId };\n throw err;\n }\n}\n\n/** Generate a record id. Used by POST when the form doesn't carry a\n * primary-key value (UI shortcut — Claude normally derives a semantic\n * id from the record's name). Delegates to the isomorphic generator, so\n * the id the UI pre-fills and the id a blank-id POST mints stay the\n * same thing rather than two implementations that have to agree. */\nexport function generateItemId(): string {\n return newItemId();\n}\n\n/** The item id a CREATE should use for `schema`, or null when the\n * caller should generate one. A singleton collection pins every\n * create to its fixed `schema.singleton` id, so the \"at most one\n * record\" contract is enforced server-side (a second create targets\n * the same file and hits `writeItem`'s refuseOverwrite conflict) —\n * not only in the UI. Otherwise the record's own primaryKey value\n * wins, falling back to a generated id (null = \"generate\"). */\nexport function resolveCreateItemId(schema: CollectionSchema, record: CollectionItem): string | null {\n if (schema.singleton) return schema.singleton;\n const primaryRaw = record[schema.primaryKey];\n return typeof primaryRaw === \"string\" && primaryRaw.length > 0 ? primaryRaw : null;\n}\n","// The aggregation-query DSL for `dataSource` collections (v2 of\n// plans/done/feat-collection-csv-duckdb-source.md). A structured JSON query —\n// NEVER raw SQL — because SQL-the-language isn't scoped to the data file\n// (`read_csv`/`read_text`/`COPY TO` reach the whole filesystem) and the\n// query authors (custom views, the agent) are influenceable by untrusted\n// content. The DSL is safe by construction: column names / aliases become\n// quoted identifiers, every VALUE rides as a prepared-statement parameter,\n// and the shape can't express file access at all. Compiled to SQL\n// server-side (`collection/server/csvQuery.ts`).\n//\n// Isomorphic: custom views build these objects in the browser; the server\n// validates with the same zod schema before compiling.\n\nimport { z } from \"zod\";\n\n/** Result-column aliases double as SQL identifiers and JSON keys — keep\n * them to a conservative identifier charset so neither side needs\n * escaping gymnastics. */\nconst SAFE_ALIAS_PATTERN = /^[A-Za-z_]\\w{0,63}$/;\n\n/** Hard ceiling on returned rows; `limit` clamps below it. A group-by on\n * a near-unique column would otherwise return one row per source row —\n * the exact materialization the aggregate path exists to avoid. */\nexport const MAX_QUERY_ROWS = 10000;\n/** Default row cap when the query declares no `limit`. */\nexport const DEFAULT_QUERY_ROWS = 1000;\n/** Cap on aggregate expressions per query — a record shape has no\n * intrinsic size limit, and thousands of expressions would make one\n * full-file scan arbitrarily wide. */\nexport const MAX_QUERY_AGGREGATES = 32;\n\n/** One aggregate column: `count` (rows; `column` optional to count\n * non-null cells) or `sum`/`avg`/`min`/`max` over a named CSV column. */\nexport const QueryAggregateZ = z\n .object({\n op: z.enum([\"count\", \"sum\", \"avg\", \"min\", \"max\"]),\n column: z.string().min(1).optional(),\n })\n .refine((aggregate) => aggregate.op === \"count\" || aggregate.column !== undefined, {\n message: \"`column` is required for every aggregate op except `count`\",\n path: [\"column\"],\n });\n\n/** One filter condition. Same op vocabulary as the schema-level `where`\n * (`core/where.ts`) so authors learn one set; values may be typed\n * (number / boolean) since CSV columns are. `in` requires an array\n * value, every other op a scalar. */\nexport const QueryWhereZ = z\n .object({\n field: z.string().min(1),\n op: z.enum([\"eq\", \"ne\", \"in\", \"gt\", \"gte\", \"lt\", \"lte\", \"contains\"]),\n value: z.union([\n z.string(),\n z.number(),\n z.boolean(),\n z\n .array(z.union([z.string(), z.number(), z.boolean()]))\n .min(1)\n .max(100),\n ]),\n })\n .refine((cond) => (cond.op === \"in\") === Array.isArray(cond.value), {\n message: \"`in` requires an array value (the allowed set); every other op requires a scalar value\",\n path: [\"value\"],\n });\n\nexport const QueryOrderZ = z.object({\n /** A `groupBy` column or an aggregate alias — membership enforced by\n * the whole-query refine below. */\n field: z.string().min(1),\n dir: z.enum([\"asc\", \"desc\"]).optional(),\n});\n\n/** The whole query. At least one of `groupBy` / `aggregates` must be\n * present: bare `groupBy` is a DISTINCT listing, bare `aggregates` a\n * whole-file scalar row, together a grouped aggregation. */\nexport const CollectionQueryZ = z\n .object({\n // Case-insensitively unique: DuckDB treats `category` and `Category`\n // as the SAME identifier, groups once, and auto-renames the duplicate\n // SELECT column (`Category_1`) — silently breaking the result-key\n // contract. Same rule as the alias refine below.\n groupBy: z\n .array(z.string().min(1))\n .max(8)\n .refine((columns) => new Set(columns.map((column) => column.toLowerCase())).size === columns.length, {\n message: \"`groupBy` columns must be unique (case-insensitively — SQL identifiers ignore case)\",\n })\n .optional(),\n aggregates: z\n .record(z.string().regex(SAFE_ALIAS_PATTERN, \"aggregate aliases must be simple identifiers (letters/digits/underscore)\"), QueryAggregateZ)\n .optional(),\n where: z.array(QueryWhereZ).max(16).optional(),\n orderBy: z.array(QueryOrderZ).max(4).optional(),\n limit: z.number().int().min(1).max(MAX_QUERY_ROWS).optional(),\n })\n .refine((query) => (query.groupBy?.length ?? 0) > 0 || Object.keys(query.aggregates ?? {}).length > 0, {\n message: \"declare at least one of `groupBy` (columns to bucket by) or `aggregates` (values to compute)\",\n path: [\"groupBy\"],\n })\n .refine((query) => Object.keys(query.aggregates ?? {}).length <= MAX_QUERY_AGGREGATES, {\n message: `\\`aggregates\\` supports at most ${MAX_QUERY_AGGREGATES} entries`,\n path: [\"aggregates\"],\n })\n // An alias shadowing a groupBy column would make the SELECT list (and\n // the result object) ambiguous. CASE-INSENSITIVE: DuckDB treats\n // identifiers that differ only by case as the same name (quoting does\n // not opt out), so `Total` vs `total` collide there even though they\n // are distinct JSON keys here — reject both same-case and cross-case\n // collisions, including between two aliases.\n .refine(\n (query) => {\n const groupLower = new Set((query.groupBy ?? []).map((column) => column.toLowerCase()));\n const seen = new Set<string>();\n return Object.keys(query.aggregates ?? {}).every((alias) => {\n const lower = alias.toLowerCase();\n if (groupLower.has(lower) || seen.has(lower)) return false;\n seen.add(lower);\n return true;\n });\n },\n {\n message: \"aggregate aliases must be unique and must not collide with `groupBy` column names (case-insensitively — SQL identifiers ignore case)\",\n path: [\"aggregates\"],\n },\n )\n // `orderBy` can only sort what the result actually contains.\n .refine(\n (query) => {\n const sortable = new Set([...(query.groupBy ?? []), ...Object.keys(query.aggregates ?? {})]);\n return (query.orderBy ?? []).every((order) => sortable.has(order.field));\n },\n {\n message: \"every `orderBy.field` must be a `groupBy` column or an aggregate alias\",\n path: [\"orderBy\"],\n },\n );\n\nexport type CollectionQueryAggregate = z.infer<typeof QueryAggregateZ>;\nexport type CollectionQueryWhere = z.infer<typeof QueryWhereZ>;\nexport type CollectionQueryOrder = z.infer<typeof QueryOrderZ>;\nexport type CollectionQuery = z.infer<typeof CollectionQueryZ>;\n","// Compile a validated CollectionQuery (the structured aggregation DSL,\n// `core/queryZ.ts`) into DuckDB SQL over `read_csv`. Safety model:\n// - column names / aliases → double-quoted identifiers (quoteIdent);\n// - every comparison VALUE → a prepared-statement parameter;\n// - the file path is bound by the EXECUTOR as the first parameter;\n// - the DSL itself can't name a table function, so no query can reach\n// any file other than the one the executor binds.\n// Pure + exported for unit tests — no filesystem, no DuckDB here.\n\nimport type { CollectionQuery, CollectionQueryAggregate, CollectionQueryWhere } from \"../core/queryZ\";\nimport { DEFAULT_QUERY_ROWS } from \"../core/queryZ\";\n\n/** One value bound to a `?` placeholder. The DSL only ever compares\n * against JSON scalars, and these are exactly the `DuckDBValue`s the\n * driver accepts — typing the chain end-to-end is what lets `queryCsv`\n * hand them to `runAndReadAll` without coercion. */\nexport type CsvQueryParam = string | number | boolean;\n\n/** Double-quote a SQL identifier (CSV column name / result alias). */\nexport function quoteIdent(name: string): string {\n return `\"${name.replaceAll('\"', '\"\"')}\"`;\n}\n\n/** Single-quote a SQL string literal (a `types={...}` struct key). */\nexport function quoteLiteral(value: string): string {\n return `'${value.replaceAll(\"'\", \"''\")}'`;\n}\n\n/** The `read_csv` argument list shared by every CSV query: the (prepared)\n * path plus a `types` pin forcing the key column to VARCHAR — without it\n * DuckDB's sniffer turns `001` into BIGINT 1, so leading zeros vanish\n * and distinct keys collapse. */\nexport function readCsvArgs(primaryKey: string): string {\n return `?, types={${quoteLiteral(primaryKey)}: 'VARCHAR'}`;\n}\n\n/** One aggregate's SQL expression. `sum`/`avg` TRY_CAST to DOUBLE so a\n * column the sniffer kept as VARCHAR (mixed values) aggregates over its\n * numeric cells instead of erroring; non-numeric cells become NULL and\n * are skipped — standard BI tolerance. `min`/`max` stay native (they are\n * meaningful on strings and dates too). */\nfunction aggregateExpr(aggregate: CollectionQueryAggregate): string {\n const { op, column } = aggregate;\n if (op === \"count\") return column === undefined ? \"count(*)\" : `count(${quoteIdent(column)})`;\n if (op === \"sum\" || op === \"avg\") return `${op}(TRY_CAST(${quoteIdent(column ?? \"\")} AS DOUBLE))`;\n return `${op}(${quoteIdent(column ?? \"\")})`;\n}\n\n/** One where condition → SQL fragment + its bound parameters. String\n * equality compares against `CAST(col AS VARCHAR)` so a sniffer-typed\n * column still matches its textual value; numeric/boolean values compare\n * natively (DuckDB coerces the column side). */\nfunction whereFragment(cond: CollectionQueryWhere): { sql: string; params: CsvQueryParam[] } {\n const column = quoteIdent(cond.field);\n const asText = `CAST(${column} AS VARCHAR)`;\n if (cond.op === \"in\") {\n const values = arrayValue(cond);\n const textual = values.every((value) => typeof value === \"string\");\n const lhs = textual ? asText : column;\n return { sql: `${lhs} IN (${values.map(() => \"?\").join(\", \")})`, params: values };\n }\n // `String()` stays — `contains` matches against text, so a numeric needle is\n // searched by its text form — but an array must fail here like it does on\n // every other scalar op, not silently become the needle \"1,2\".\n if (cond.op === \"contains\") return { sql: `contains(${asText}, ?)`, params: [String(scalarValue(cond))] };\n const operator = { eq: \"=\", ne: \"<>\", gt: \">\", gte: \">=\", lt: \"<\", lte: \"<=\" }[cond.op];\n const lhs = typeof cond.value === \"string\" && (cond.op === \"eq\" || cond.op === \"ne\") ? asText : column;\n return { sql: `${lhs} ${operator} ?`, params: [scalarValue(cond)] };\n}\n\n/** Mirror of `scalarValue` for the one op that takes a set: a scalar under\n * `in` also means the query skipped `CollectionQueryZ`. Left unchecked it\n * failed as `values.every is not a function`, naming neither the field nor\n * the op. */\nfunction arrayValue(cond: CollectionQueryWhere): CsvQueryParam[] {\n if (!Array.isArray(cond.value)) {\n throw new Error(`where condition on '${cond.field}' uses op 'in', which requires an array value, not a scalar`);\n }\n return cond.value;\n}\n\n/** `CollectionQueryZ` refines \"`in` ⇔ array value\", so an array reaching a\n * scalar op means the query was compiled without being validated first —\n * binding it would send an array to a single `?`. */\nfunction scalarValue(cond: CollectionQueryWhere): CsvQueryParam {\n if (Array.isArray(cond.value)) {\n throw new Error(`where condition on '${cond.field}' uses op '${cond.op}', which requires a scalar value, not an array`);\n }\n return cond.value;\n}\n\n/** Compile a validated query against `fromSql` (a table-function call\n * whose FIRST placeholder is the source path — the executor binds it).\n * Returns the SQL and the where-value parameters that follow the path.\n * Callers MUST have run `CollectionQueryZ` first; this function trusts\n * the shape (aliases already charset-checked, orderBy membership already\n * enforced). */\nfunction compileQuery(query: CollectionQuery, fromSql: string): { sql: string; params: CsvQueryParam[] } {\n const groupBy = query.groupBy ?? [];\n const aggregates = Object.entries(query.aggregates ?? {});\n const selectList = [...groupBy.map(quoteIdent), ...aggregates.map(([alias, aggregate]) => `${aggregateExpr(aggregate)} AS ${quoteIdent(alias)}`)];\n const where = (query.where ?? []).map(whereFragment);\n const clauses = [`SELECT ${selectList.join(\", \")}`, `FROM ${fromSql}`];\n if (where.length > 0) clauses.push(`WHERE ${where.map((fragment) => fragment.sql).join(\" AND \")}`);\n if (groupBy.length > 0) clauses.push(`GROUP BY ${groupBy.map(quoteIdent).join(\", \")}`);\n const orderBy = (query.orderBy ?? []).map((order) => quoteIdent(order.field) + (order.dir === \"desc\" ? \" DESC\" : \" ASC\"));\n if (orderBy.length > 0) clauses.push(`ORDER BY ${orderBy.join(\", \")}`);\n clauses.push(`LIMIT ${query.limit ?? DEFAULT_QUERY_ROWS}`);\n return { sql: clauses.join(\" \"), params: where.flatMap((fragment) => fragment.params) };\n}\n\n/** Compile against a CSV file (the dataSource store's engine). */\nexport function compileCsvQuery(query: CollectionQuery, primaryKey: string): { sql: string; params: CsvQueryParam[] } {\n return compileQuery(query, `read_csv(${readCsvArgs(primaryKey)})`);\n}\n\n/** Compile against a JSONL file of ENRICHED records — the file-backed\n * collections' engine (see `jsonlQuery.ts`). No VARCHAR key pin needed:\n * enriched record ids are already strings. `sample_size=-1` makes the\n * schema inference scan EVERY line — with the default sample, a sparse\n * optional/derived field first appearing past the sample would not be\n * inferred as a column and the query would binder-error on it (Codex P2\n * on #2165). The full scan costs nothing extra here: aggregation reads\n * the whole file anyway. */\nexport function compileJsonlQuery(query: CollectionQuery): { sql: string; params: CsvQueryParam[] } {\n return compileQuery(query, `read_json(?, format='newline_delimited', sample_size=-1)`);\n}\n","// DuckDB-backed read-only store over an external CSV file (schema\n// `dataSource`, v1). The user's file is the source of truth and is NEVER\n// modified: non-UTF-8 files (Shift_JIS rosters are the primary use case)\n// are decoded to a content-addressed UTF-8 cache copy under os.tmpdir()\n// and DuckDB reads that copy.\n//\n// Row → record contract:\n// - the schema's `primaryKey` names the CSV column whose value becomes\n// the record id; CSV columns map to fields by NAME;\n// - a key value that isn't a safe record id (Japanese, spaces, …) is\n// hex-encoded (`id0x…`) so detail URLs / remote-view addressing keep\n// working — `displayField` covers presentation;\n// - duplicate key values: LAST row wins (a warn is logged); rows with an\n// empty/missing key are skipped (warn);\n// - `list()` is capped at MAX_CSV_ROWS — the whole existing UI\n// materializes every record, so an uncapped 2M-row file would be a\n// memory bomb. v2 replaces the cap with paging + native aggregation.\n//\n// SQL safety: the row-value comparison is a prepared-statement parameter,\n// never string concatenation; the key column is identifier-quoted. The\n// file path itself is host-resolved (workspace containment in discovery)\n// and also bound as a parameter.\n//\n// DuckDB is a native module; `import()` failures (unsupported platform,\n// broken install) degrade to a thrown, clearly-worded error so ONLY\n// dataSource collections break — see\n// packages/core/assets/helps/error-recovery.md.\n\nimport { fieldTextOrNull } from \"../core/fieldText\";\nimport { BackendUnavailableError } from \"./backendAvailability\";\nimport { lstat, mkdir, open, readdir, readFile, rename, stat, unlink, writeFile } from \"node:fs/promises\";\nimport { createHash, randomBytes } from \"node:crypto\";\nimport { tmpdir } from \"node:os\";\nimport path from \"node:path\";\nimport iconv from \"iconv-lite\";\nimport type { CollectionItem } from \"../core/schema\";\nimport type { CollectionQuery } from \"../core/queryZ\";\nimport type { DuckDBInstance } from \"@duckdb/node-api\";\nimport { compileCsvQuery, quoteIdent, readCsvArgs, type CsvQueryParam } from \"./csvQuery\";\nimport { getWorkspaceRoot, log } from \"./host\";\nimport { isContainedInRoot, safeRecordId } from \"./paths\";\nimport { isErrorWithCode } from \"@mulmoclaude/common\";\n\n/** `list()` row cap. Over-cap files are truncated with a warn — the v1\n * contract is \"browse + per-record views\", not full-table analytics. */\nexport const MAX_CSV_ROWS = 5000;\n\n/** Record ids minted from non-safe key values: `id0x` + utf-8 hex. Raw key\n * values that themselves match this pattern are ALSO encoded, so the\n * encoded namespace never collides with a raw value (injective mapping). */\nconst ENCODED_ID_PATTERN = /^id0x([0-9a-f]+)$/;\n\n/** A CSV key value → the record id it's addressed by. Safe values pass\n * through untouched; everything else (and anything shaped like an encoded\n * id) becomes `id0x<hex>`. Pure + exported for unit tests. */\nexport function encodeCsvRecordId(rawKey: string): string {\n if (safeRecordId(rawKey) === rawKey && !ENCODED_ID_PATTERN.test(rawKey)) return rawKey;\n return `id0x${Buffer.from(rawKey, \"utf-8\").toString(\"hex\")}`;\n}\n\n/** A record id → the CSV key value to look up. Inverse of\n * `encodeCsvRecordId` for encoded ids; anything else is already the raw\n * value. Pure + exported for unit tests. */\nexport function decodeCsvRecordId(itemId: string): string {\n const hex = ENCODED_ID_PATTERN.exec(itemId)?.[1];\n if (hex === undefined) return itemId;\n return Buffer.from(hex, \"hex\").toString(\"utf-8\");\n}\n\n/** Normalize one DuckDB JS value into a JSON-safe record value: BigInt →\n * number (string beyond the safe range), DATE/TIMESTAMP → ISO string\n * (date-only when the clock is exactly UTC midnight, matching the `date`\n * field contract), exotic DuckDB values → their string form. Pure +\n * exported for unit tests. */\n/** `JSON.stringify` restricted to what a CSV cell can survive. Returns the\n * serialised value, or `String(value)` when serialisation is impossible —\n * losing the content of one cell is bad, failing the entire query is worse. */\nfunction safeJsonCell(value: object): string {\n try {\n const json = JSON.stringify(value, (_key, entry: unknown) => (typeof entry === \"bigint\" ? entry.toString() : entry));\n // eslint-disable-next-line @typescript-eslint/no-base-to-string -- last resort: JSON.stringify returned undefined (an object whose toJSON does). \"[object Object]\" at least says \"this cell held an object\"; \"\" would erase that it existed.\n return json ?? String(value);\n } catch {\n // eslint-disable-next-line @typescript-eslint/no-base-to-string -- the serialiser threw (circular ref). Same reasoning as above: a placeholder beats losing the cell entirely.\n return String(value);\n }\n}\n\nexport function normalizeCsvValue(value: unknown): unknown {\n if (typeof value === \"bigint\") {\n return value <= BigInt(Number.MAX_SAFE_INTEGER) && value >= BigInt(-Number.MAX_SAFE_INTEGER) ? Number(value) : value.toString();\n }\n if (value instanceof Date) {\n const iso = value.toISOString();\n return iso.endsWith(\"T00:00:00.000Z\") ? iso.slice(0, 10) : iso;\n }\n // A DuckDB STRUCT / LIST / MAP arrives as an object. `String(...)` renders it\n // \"[object Object]\" — the cell's content is simply gone. Serialise instead:\n // this is a data cell, not an id or a matcher key, so preserving what the\n // column holds beats reducing it to a placeholder.\n //\n // Guarded, because `JSON.stringify` is not total on what DuckDB can hand us:\n // it throws on a BIGINT nested in a struct (the branch above only unwraps a\n // top-level bigint) and on a circular ref, and returns undefined when an\n // object's `toJSON` does. A cell that cannot be serialised falls back to the\n // old rendering rather than aborting the whole read.\n if (value !== null && typeof value === \"object\") return safeJsonCell(value);\n return value;\n}\n\n/** One raw DuckDB row → a CollectionItem, or null when the key cell is\n * missing/empty (the row can't be addressed). The primaryKey field is\n * OVERWRITTEN with the (possibly encoded) record id so `item[primaryKey]`\n * and the record's address never drift — same invariant the file store's\n * write path enforces. Pure + exported for unit tests. */\nexport function csvRowToItem(row: Record<string, unknown>, primaryKey: string): CollectionItem | null {\n const normalized = Object.fromEntries(Object.entries(row).map(([key, value]) => [key, normalizeCsvValue(value)]));\n const rawKey = normalized[primaryKey];\n // Identity: same rule as every other primaryKey read. `normalizeCsvValue` has\n // already flattened objects to JSON, so anything without a text form here is\n // genuinely unaddressable.\n const keyText = fieldTextOrNull(rawKey);\n if (keyText === null || keyText === \"\") return null;\n return { ...normalized, [primaryKey]: encodeCsvRecordId(keyText) };\n}\n\n/** Dedupe by record id, LAST row wins (matches `csvRead`'s last-match\n * pick). Returns the surviving items in first-seen order. Pure +\n * exported for unit tests. */\nexport function dedupeByRecordId(items: CollectionItem[], primaryKey: string): { items: CollectionItem[]; duplicates: number } {\n const byId = new Map<string, CollectionItem>();\n for (const item of items) byId.set(String(item[primaryKey]), item);\n return { items: [...byId.values()], duplicates: items.length - byId.size };\n}\n\n/** True when a thrown DuckDB error is the `types` pin naming a column the\n * CSV doesn't have — the schema/file-mismatch case the caller downgrades\n * to \"empty collection + warn\" instead of a 500. */\nfunction isMissingKeyColumnError(err: unknown): boolean {\n return String(err).includes(\"do not exist in the CSV\");\n}\n\n// ---------------------------------------------------------------------------\n// Encoding: never touch the user's file — decode to a tmpdir cache copy\n// ---------------------------------------------------------------------------\n\n/** Bytes sniffed for UTF-8 validity. The trailing 3 bytes of the sample\n * are dropped so a multibyte char split at the boundary can't produce a\n * false negative on a valid file. */\nconst SNIFF_BYTES = 1024 * 1024;\n\nfunction isValidUtf8(buf: Buffer): boolean {\n try {\n new TextDecoder(\"utf-8\", { fatal: true }).decode(buf);\n return true;\n } catch {\n return false;\n }\n}\n\n/** Detect the (best-effort) encoding of a non-UTF-8 buffer. BOMs decide\n * UTF-16; otherwise cp932 (the Shift_JIS superset — Excel-exported\n * Japanese CSVs are the primary non-UTF-8 case this feature serves). */\nfunction fallbackEncoding(buf: Buffer): string {\n if (buf.length >= 2 && buf[0] === 0xff && buf[1] === 0xfe) return \"utf-16le\";\n if (buf.length >= 2 && buf[0] === 0xfe && buf[1] === 0xff) return \"utf-16be\";\n return \"cp932\";\n}\n\nexport function cacheDir(): string {\n return path.join(tmpdir(), \"mulmoclaude-csv-utf8\");\n}\n\n/** Read only the first `bytes` of a file — the encoding sniff must not\n * pull a multi-hundred-MB CSV into memory on the (common) UTF-8 path. */\nasync function readHead(absPath: string, bytes: number): Promise<Buffer> {\n const handle = await open(absPath, \"r\");\n try {\n const { size } = await handle.stat();\n const buf = Buffer.alloc(Math.min(bytes, size));\n await handle.read(buf, 0, buf.length, 0);\n return buf;\n } finally {\n await handle.close();\n }\n}\n\n/** Decode the whole file into a UTF-8 cache copy and return its path.\n * Cache key = (path, mtime, size), so a replaced CSV re-decodes and an\n * unchanged one never does. */\nasync function pathExists(target: string): Promise<boolean> {\n try {\n await stat(target);\n return true;\n } catch {\n return false;\n }\n}\n\n/** Best-effort removal of older decode-cache entries for the same source\n * path — a frequently-replaced large CSV would otherwise accumulate one\n * full copy per (mtime, size) forever. Runs AFTER the current copy is\n * published; a concurrent reader holding an old fd is unaffected\n * (unlink-while-open is safe on POSIX). */\nasync function evictSupersededCache(key: string, keepBasename: string): Promise<void> {\n try {\n const entries = await readdir(cacheDir());\n await Promise.all(\n entries.filter((name) => name.startsWith(`${key}-`) && name !== keepBasename).map((name) => unlink(path.join(cacheDir(), name)).catch(() => undefined)),\n );\n } catch {\n // cache dir missing / unreadable — nothing to evict\n }\n}\n\n/** Decode the whole file into a UTF-8 cache copy and return its path.\n * Cache key = (path, mtime, size), so a replaced CSV re-decodes and an\n * unchanged one never does; superseded copies are evicted. The cache\n * lives in the SHARED OS tmpdir, so the dir is 0700 and files 0600 —\n * decoded rows must not be readable by other local users. */\nasync function decodeToCache(absPath: string, info: { mtimeMs: number; size: number }): Promise<string> {\n const key = createHash(\"sha256\").update(absPath).digest(\"hex\").slice(0, 16);\n const cached = path.join(cacheDir(), `${key}-${Math.trunc(info.mtimeMs)}-${info.size}.csv`);\n if (!(await pathExists(cached))) {\n const whole = await readFile(absPath);\n const encoding = fallbackEncoding(whole);\n const text = iconv.decode(whole, encoding);\n await mkdir(cacheDir(), { recursive: true, mode: 0o700 });\n // Unique tmp name + rename in the SAME dir — atomic publish, and a\n // concurrent decode of the same file just wins/loses the rename cleanly.\n const tmp = `${cached}.${randomBytes(4).toString(\"hex\")}.tmp`;\n await writeFile(tmp, text, { encoding: \"utf-8\", mode: 0o600 });\n await rename(tmp, cached);\n log.info(\"collections\", \"decoded non-UTF-8 dataSource file to cache\", { path: absPath, encoding });\n await evictSupersededCache(key, path.basename(cached));\n }\n return cached;\n}\n\n/** Re-validate the dataSource file at READ time, mirroring the JSON\n * store's per-read defenses: realpath containment (a symlink swapped in\n * after discovery must not walk out of the workspace) and an lstat\n * regular-file check (a symlink leaf is refused outright, even one\n * pointing inside the workspace — same rule as `isRegularFile` on\n * record files). Returns the stat info, or null for \"no readable file\"\n * (ENOENT / refused), which callers render as an empty collection. */\nasync function safeCsvStat(absPath: string, workspaceRoot: string): Promise<{ mtimeMs: number; size: number } | null> {\n if (!isContainedInRoot(absPath, workspaceRoot)) {\n log.warn(\"collections\", \"dataSource read refused: path escapes workspace\", { path: absPath });\n return null;\n }\n let info;\n try {\n info = await lstat(absPath);\n } catch (err) {\n if (isErrorWithCode(err) && err.code === \"ENOENT\") return null;\n throw err;\n }\n if (!info.isFile()) {\n log.warn(\"collections\", \"dataSource read refused: not a regular file (symlink?)\", { path: absPath });\n return null;\n }\n return info;\n}\n\n/** Return a path DuckDB can read as UTF-8: the original file when it\n * already is UTF-8 (the cheap, common case — only the head is sniffed),\n * else a decoded cache copy (see `decodeToCache`). Returns null when\n * there is no readable file (missing, symlink, or containment-refused —\n * see `safeCsvStat`), which callers render as an empty collection. */\nasync function ensureUtf8CsvPath(absPath: string, workspaceRoot: string): Promise<string | null> {\n const info = await safeCsvStat(absPath, workspaceRoot);\n if (info === null) return null;\n const head = await readHead(absPath, SNIFF_BYTES);\n // Drop the tail bytes of a full-length sample so a multibyte char split\n // at the boundary can't read as invalid UTF-8.\n const sample = head.length === SNIFF_BYTES ? head.subarray(0, SNIFF_BYTES - 3) : head;\n const hasUtf16Bom = head.length >= 2 && ((head[0] === 0xff && head[1] === 0xfe) || (head[0] === 0xfe && head[1] === 0xff));\n if (!hasUtf16Bom && isValidUtf8(sample)) return absPath;\n return decodeToCache(absPath, info);\n}\n\n// ---------------------------------------------------------------------------\n// DuckDB plumbing\n// ---------------------------------------------------------------------------\n\nlet instancePromise: Promise<DuckDBInstance> | null = null;\n\n/** Lazily create one shared in-memory DuckDB instance. The dynamic import\n * keeps the native module OUT of core's load path — a platform where the\n * prebuilt binding is missing degrades to a per-query error on dataSource\n * collections only, never a broken core. A failed init is retried on the\n * next call (the promise is reset). */\nasync function duckDbInstance(): Promise<DuckDBInstance> {\n if (instancePromise === null) {\n instancePromise = import(\"@duckdb/node-api\").then((mod) => mod.DuckDBInstance.create(\":memory:\"));\n }\n try {\n return await instancePromise;\n } catch (err) {\n instancePromise = null;\n throw new BackendUnavailableError(\n `DuckDB is unavailable on this host (@duckdb/node-api failed to load: ${String(err)}) — dataSource collections cannot be read`,\n );\n }\n}\n\nexport async function queryCsv(sql: string, params: CsvQueryParam[]): Promise<Record<string, unknown>[]> {\n const instance = await duckDbInstance();\n const connection = await instance.connect();\n try {\n const reader = await connection.runAndReadAll(sql, params);\n return reader.getRowObjectsJS();\n } finally {\n connection.disconnectSync();\n }\n}\n\n// ---------------------------------------------------------------------------\n// The store operations (consumed by storeFor in ./store)\n// ---------------------------------------------------------------------------\n\n/** Every row of the CSV as records — capped, deduped, id-encoded. The\n * key column is pinned to VARCHAR (see `readCsvArgs`). `workspaceRoot`\n * drives the per-read containment check; omitted, the configured host\n * root is used. */\nexport interface CsvListResult {\n items: CollectionItem[];\n /** True when the scan stopped at `MAX_CSV_ROWS` — `items` is then a\n * row-capped prefix, not the whole file. Surfaced to `ListPage.truncated`\n * so callers see the cap instead of inferring it from a warn log. */\n truncated: boolean;\n}\n\nexport async function csvList(absPath: string, primaryKey: string, workspaceRoot?: string): Promise<CsvListResult> {\n const utf8Path = await ensureUtf8CsvPath(absPath, workspaceRoot ?? getWorkspaceRoot());\n if (utf8Path === null) return { items: [], truncated: false };\n let rows: Record<string, unknown>[];\n try {\n rows = await queryCsv(`SELECT * FROM read_csv(${readCsvArgs(primaryKey)}) LIMIT ${MAX_CSV_ROWS + 1}`, [utf8Path]);\n } catch (err) {\n // The VARCHAR pin names a column the CSV doesn't have — a schema/file\n // mismatch, rendered as an empty collection with a warn (same outcome\n // the pre-pin `primaryKey in row` check produced), not a 500.\n if (!isMissingKeyColumnError(err)) throw err;\n log.warn(\"collections\", \"dataSource CSV has no primaryKey column — every row is skipped\", { path: absPath, primaryKey });\n return { items: [], truncated: false };\n }\n const truncated = rows.length > MAX_CSV_ROWS;\n if (truncated) {\n log.warn(\"collections\", \"dataSource CSV truncated to row cap\", { path: absPath, cap: MAX_CSV_ROWS });\n rows.length = MAX_CSV_ROWS;\n }\n const items = rows.map((row) => csvRowToItem(row, primaryKey)).filter((item): item is CollectionItem => item !== null);\n const skipped = rows.length - items.length;\n if (skipped > 0) log.warn(\"collections\", \"dataSource CSV rows skipped (empty key cell)\", { path: absPath, skipped });\n const deduped = dedupeByRecordId(items, primaryKey);\n if (deduped.duplicates > 0)\n log.warn(\"collections\", \"dataSource CSV has duplicate key values (last row wins)\", { path: absPath, duplicates: deduped.duplicates });\n return { items: deduped.items, truncated };\n}\n\n/** The scan-order ordinal column the last-match read adds. Underscore\n * prefix keeps it out of any plausible CSV header namespace; it is\n * stripped from the returned record either way. */\nconst ROW_ORDINAL = \"__mc_row\";\n\n/** One record by id. The comparison value rides as a prepared-statement\n * parameter, and the LAST matching row is selected IN DuckDB (scan-order\n * ordinal + LIMIT 1) — a CSV with thousands of duplicate keys must not\n * materialize them all for one detail read. Consistent with csvList's\n * last-wins dedupe. */\nexport async function csvRead(absPath: string, primaryKey: string, itemId: string, workspaceRoot?: string): Promise<CollectionItem | null> {\n const utf8Path = await ensureUtf8CsvPath(absPath, workspaceRoot ?? getWorkspaceRoot());\n if (utf8Path === null) return null;\n const rawKey = decodeCsvRecordId(itemId);\n // Errors (missing key column, malformed CSV, DuckDB unavailable)\n // propagate — a clear 500 with the DuckDB message beats a silent 404.\n const sql =\n `SELECT * FROM (SELECT *, row_number() OVER () AS ${quoteIdent(ROW_ORDINAL)} FROM read_csv(${readCsvArgs(primaryKey)})) ` +\n `WHERE CAST(${quoteIdent(primaryKey)} AS VARCHAR) = ? ORDER BY ${quoteIdent(ROW_ORDINAL)} DESC LIMIT 1`;\n const rows = await queryCsv(sql, [utf8Path, rawKey]);\n const last = rows.at(0);\n if (last === undefined) return null;\n const { [ROW_ORDINAL]: __ordinal, ...record } = last;\n return csvRowToItem(record, primaryKey);\n}\n\n/** Run a validated aggregation query (the structured DSL — see\n * `core/queryZ.ts`) over the WHOLE file: no row cap on the scan (a\n * capped aggregate would be a wrong number), only the result-row LIMIT\n * the compiler emits. Values are normalized like list/read rows so a\n * chart consumer gets plain JSON scalars. */\nexport async function csvRunQuery(absPath: string, primaryKey: string, query: CollectionQuery, workspaceRoot?: string): Promise<Record<string, unknown>[]> {\n const utf8Path = await ensureUtf8CsvPath(absPath, workspaceRoot ?? getWorkspaceRoot());\n if (utf8Path === null) return [];\n const { sql, params } = compileCsvQuery(query, primaryKey);\n const rows = await queryCsv(sql, [utf8Path, ...params]);\n return rows.map((row) => Object.fromEntries(Object.entries(row).map(([key, value]) => [key, normalizeCsvValue(value)])));\n}\n","// Shared `fs.watch` plumbing for the filesystem-backed stores.\n//\n// Every filesystem backend hits the same three quirks, so they are solved\n// once here rather than in each store:\n//\n// - `fs.watch` throws on a missing directory, so the watch has to be armed\n// lazily and re-armed rather than assumed;\n// - a filename arrives as `null` on some platforms (we then can't say which\n// record changed, only that something did);\n// - `filename` is typed `string` but can arrive as a Buffer, which has no\n// `startsWith` — calling it directly throws inside the callback and takes\n// the watcher down with it.\n//\n// Single-artifact backends (one CSV, one db file) additionally watch the\n// PARENT directory rather than the file: an atomic replace swaps the inode,\n// and a watch bound to the old one goes silently deaf.\n\nimport { realpathSync, watch, type FSWatcher } from \"node:fs\";\nimport { mkdir } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport { log } from \"./host\";\n\n/** An atomic file replace (editor save, `mv` over the target) surfaces as\n * 2-3 events. Collapse them so one user action reports one change. */\nconst REPLACE_DEBOUNCE_MS = 300;\n\n/** The path to hand `watch()`, with Windows 8.3 short names resolved away.\n *\n * ReadDirectoryChangesW reports filenames against the LONG path, but a watch\n * opened on a short path (`C:\\Users\\RUNNER~1\\…` — what `os.tmpdir()` returns\n * on GitHub's Windows runners) keeps the short form. libuv's\n * `assert(!_wcsnicmp(filename, dir, dirlen))` in `src/win/fs-event.c` then\n * aborts the PROCESS on the first event — a native assert, so neither\n * `watcher.on(\"error\")` nor a try/catch can contain it.\n *\n * POSIX is deliberately left alone: `realpath` there also collapses symlinks\n * (`/var` → `/private/var` on macOS), which we neither need nor want to\n * change. A failure falls back to the original path — worst case we are no\n * worse off than before. */\nfunction watchablePath(dir: string): string {\n if (process.platform !== \"win32\") return dir;\n try {\n return realpathSync.native(dir);\n } catch {\n return dir;\n }\n}\n\nexport interface FsWatchHandle {\n close: () => void;\n}\n\n/** Watch `dir`, reporting each accepted filename. `accept` decides what is\n * noise; a null filename always passes (the platform didn't tell us which\n * file, so the caller must assume the worst). */\nexport async function watchDirectory(\n dir: string,\n accept: (filename: string) => boolean,\n onHit: (filename: string | null) => void,\n): Promise<FsWatchHandle | null> {\n try {\n await mkdir(dir, { recursive: true });\n const watcher: FSWatcher = watch(watchablePath(dir), { persistent: false }, (_eventType, rawFilename) => {\n // Defensive stringify: the callback's `string` typing is a lie on some\n // platforms, and a Buffer reaching `accept` would throw in here — which\n // kills the watcher, not just the event.\n const filename = rawFilename === null ? null : String(rawFilename);\n if (filename !== null && !accept(filename)) return;\n onHit(filename);\n });\n watcher.on(\"error\", (err) => {\n log.warn(\"collections\", \"fs watch error\", { dir, error: String(err) });\n });\n return { close: () => watcher.close() };\n } catch (err) {\n log.warn(\"collections\", \"fs watch start failed\", { dir, error: String(err) });\n return null;\n }\n}\n\n/** Watch the single file `absPath` by watching its PARENT directory, so an\n * atomic replace can't strand the watch on a dead inode. `alsoAccept`\n * widens the filter beyond the exact basename (sqlite's `-wal`/`-journal`\n * sidecars). Reports are debounced: one replace, one call. */\nexport async function watchSingleFile(\n absPath: string,\n alsoAccept: (basename: string, filename: string) => boolean,\n onChange: () => void,\n): Promise<FsWatchHandle | null> {\n const dir = path.dirname(absPath);\n const base = path.basename(absPath);\n let timer: ReturnType<typeof setTimeout> | null = null;\n const fire = (): void => {\n if (timer) clearTimeout(timer);\n timer = setTimeout(() => {\n timer = null;\n onChange();\n }, REPLACE_DEBOUNCE_MS);\n timer.unref?.();\n };\n const handle = await watchDirectory(dir, (filename) => filename === base || alsoAccept(base, filename), fire);\n if (!handle) return null;\n return {\n close: () => {\n if (timer) clearTimeout(timer);\n timer = null;\n handle.close();\n },\n };\n}\n\n/** An `FsWatchHandle` as a bare unsubscribe — `null` straight through, so an\n * unarmed watch stays distinguishable from an armed one. Lives here rather\n * than beside the store contract so both `store.ts` and the backends it\n * registers can reach it without importing each other. */\nexport function closerFor(handle: FsWatchHandle | null): (() => void) | null {\n return handle === null ? null : () => handle.close();\n}\n","// SQLite-backed WRITABLE store (schema `storage: { type: \"sqlite\" }`) —\n// the first non-file backend, added to validate the CollectionStore\n// abstraction (plans/done/refactor-storage-virtualization.md, Stage 4).\n//\n// Layout: one database file (`schema.storage.path`, workspace-contained at\n// discovery AND re-checked here), one table\n// `records(id TEXT PRIMARY KEY, record TEXT NOT NULL)` holding each\n// record's JSON. The schema's primaryKey value is the row id, gated by the\n// SAME `safeRecordId` rule as the file store's `<id>.json` names, so ids\n// stay portable across backends and remote-view preflights keep holding.\n//\n// Engine: `node:sqlite` — no native npm dependency (DuckDB pain avoided).\n// The app's engines floor is Node >= 20.12 but node:sqlite needs >= 22.5,\n// so the module is imported LAZILY and a failure degrades to a thrown,\n// clearly-worded error — ONLY sqlite-backed collections break, exactly the\n// DuckDB pattern in csvStore.ts. See\n// packages/core/assets/helps/error-recovery.md.\n//\n// Order contract: list/page walk `ORDER BY id` (BINARY collation =\n// codepoint order) — the same documented lexicographic-by-record-id order\n// as the file store. Paging is NATIVE (LIMIT/OFFSET + COUNT(*)), the\n// store's `nativePaging: true` flag is honest.\n//\n// Change events: io.ts is not involved here, so this store publishes its\n// own `publishCollectionChange` after each successful write/delete —\n// keeping the CollectionStore contract \"a successful write/delete\n// publishes\" true for every backend.\n\nimport { lstat, mkdir } from \"node:fs/promises\";\nimport { closerFor, watchSingleFile } from \"./watchFs\";\nimport { BackendUnavailableError } from \"./backendAvailability\";\nimport path from \"node:path\";\nimport { hasNumberProp, isErrorWithCode, isRecord } from \"@mulmoclaude/common\";\nimport type { CollectionItem } from \"../core/schema\";\nimport type { LoadedCollection } from \"./discoveredCollection\";\nimport type { DeleteItemResult, IoOptions, WriteItemResult } from \"./io\";\nimport { collectionChangePayload, getWorkspaceRoot, log, publishCollectionChange } from \"./host\";\nimport { isContainedInRoot, safeRecordId } from \"./paths\";\nimport { projectItemFields, type ListOptions, type ListPage, type WriteOptions } from \"./storePage\";\nimport type { CollectionStore } from \"./store\";\n\n// Minimal structural view of node:sqlite — typed locally so the build does\n// not depend on @types/node shipping the (still experimental) module types.\ninterface SqliteStatement {\n all: (...params: (string | number)[]) => unknown[];\n get: (...params: (string | number)[]) => unknown;\n run: (...params: (string | number)[]) => { changes: number | bigint };\n}\ninterface SqliteDatabase {\n prepare: (sql: string) => SqliteStatement;\n exec: (sql: string) => void;\n close: () => void;\n}\ninterface SqliteModule {\n DatabaseSync: new (dbPath: string) => SqliteDatabase;\n}\n\n/** A constructor's parameter and return types are not observable at runtime,\n * so the check stops at \"DatabaseSync is constructible\" — the only member of\n * the module this store ever touches. */\nfunction isSqliteModule(mod: unknown): mod is SqliteModule {\n return isRecord(mod) && typeof mod.DatabaseSync === \"function\";\n}\n\nlet sqliteModule: Promise<SqliteModule> | null = null;\n\n/** Drops the memo first so a later call can retry (e.g. tests stubbing the\n * runtime), then reports why the backend is unusable. */\nfunction sqliteUnavailable(reason: string): never {\n sqliteModule = null;\n throw new BackendUnavailableError(`sqlite storage needs the node:sqlite module (Node.js >= 22.5) — this runtime cannot load it: ${reason}`);\n}\n\n/** Lazy-load node:sqlite once. A runtime without it (Node < 22.5) throws a\n * clearly-worded error the caller surfaces — never a bare MODULE_NOT_FOUND. */\nfunction loadSqlite(): Promise<SqliteModule> {\n sqliteModule ??= import(\"node:sqlite\").then(\n (mod) => (isSqliteModule(mod) ? mod : sqliteUnavailable(\"the module exposes no DatabaseSync constructor\")),\n (err: unknown) => sqliteUnavailable(String(err)),\n );\n return sqliteModule;\n}\n\n/** The db file's on-disk state. A symlink or non-regular file is refused\n * (file-disclosure defense, same rule as io.ts record files); ENOENT is\n * just \"no records yet\". Any OTHER lstat failure (EACCES, EIO, …) is\n * rethrown so reads surface a real filesystem problem instead of\n * silently reporting an empty collection. */\nasync function dbFileState(absPath: string): Promise<\"missing\" | \"file\" | \"refused\"> {\n try {\n const info = await lstat(absPath);\n return info.isFile() ? \"file\" : \"refused\";\n } catch (err) {\n if (isErrorWithCode(err) && err.code === \"ENOENT\") return \"missing\";\n throw err;\n }\n}\n\nconst CREATE_TABLE = \"CREATE TABLE IF NOT EXISTS records (id TEXT PRIMARY KEY, record TEXT NOT NULL)\";\n\ntype DbHandle = { kind: \"ok\"; database: SqliteDatabase } | { kind: \"missing\" } | { kind: \"refused\" };\n\n/** Open the database for one operation, classifying the two unavailable\n * states so callers can map them honestly (`refused` ⇒ path-escape,\n * `missing` ⇒ empty / not-found — conflating them would misreport a\n * containment escape as \"item not found\"). The containment pre-check runs\n * BEFORE mkdir even when the file is missing — `isContainedInRoot`\n * resolves through the closest existing ancestor, so a symlinked-away\n * parent can never make the recursive mkdir create directories outside\n * the workspace (same pre/post belt-and-suspenders as io.ts writes). */\nasync function openDb(absPath: string, workspaceRoot: string, mode: \"read\" | \"write\"): Promise<DbHandle> {\n const state = await dbFileState(absPath);\n if (state === \"refused\") {\n log.warn(\"collections\", \"sqlite database refused: not a regular file\", { path: absPath });\n return { kind: \"refused\" };\n }\n if (!isContainedInRoot(path.dirname(absPath), workspaceRoot)) {\n log.warn(\"collections\", \"sqlite refused: database dir escapes workspace via symlink\", { path: absPath });\n return { kind: \"refused\" };\n }\n if (mode === \"read\" && state === \"missing\") return { kind: \"missing\" };\n if (mode === \"write\") {\n await mkdir(path.dirname(absPath), { recursive: true });\n if (!isContainedInRoot(path.dirname(absPath), workspaceRoot)) {\n log.warn(\"collections\", \"sqlite write refused: database dir escapes workspace via symlink (post-mkdir)\", { path: absPath });\n return { kind: \"refused\" };\n }\n }\n const { DatabaseSync } = await loadSqlite();\n const database = new DatabaseSync(absPath);\n // Wait for a concurrent writer's lock instead of failing fast with\n // SQLITE_BUSY. PRAGMA (not the constructor's `timeout` option, which\n // only exists on Node >= 22.16 — our sqlite floor is 22.5).\n database.exec(\"PRAGMA busy_timeout = 5000\");\n database.exec(CREATE_TABLE);\n return { kind: \"ok\", database };\n}\n\n/** Run `operation` against the database and always close it; unavailable\n * states resolve through `onUnavailable` so each caller maps `missing`\n * vs `refused` to its own result kind. */\nasync function withDb<T>(\n absPath: string,\n workspaceRoot: string,\n mode: \"read\" | \"write\",\n onUnavailable: (reason: \"missing\" | \"refused\") => T,\n operation: (database: SqliteDatabase) => T | Promise<T>,\n): Promise<T> {\n const handle = await openDb(absPath, workspaceRoot, mode);\n if (handle.kind !== \"ok\") return onUnavailable(handle.kind);\n try {\n return await operation(handle.database);\n } finally {\n handle.database.close();\n }\n}\n\n// SQLite extended result codes for the duplicate-id failure our INSERT hits:\n// a PRIMARY KEY clash, and the plain-UNIQUE-index equivalent.\nconst SQLITE_CONSTRAINT_PRIMARYKEY = 1555;\nconst SQLITE_CONSTRAINT_UNIQUE = 2067;\n\n/** node:sqlite throws ERR_SQLITE_ERROR with the SQLite extended result\n * code on `errcode`. Checked structurally (message text kept only as a\n * fallback for runtimes that don't expose `errcode`). */\nfunction isUniqueConstraintError(err: unknown): boolean {\n if (hasNumberProp(err, \"errcode\")) return err.errcode === SQLITE_CONSTRAINT_PRIMARYKEY || err.errcode === SQLITE_CONSTRAINT_UNIQUE;\n return String(err).includes(\"UNIQUE constraint\");\n}\n\nfunction parseRow(raw: unknown): CollectionItem | null {\n if (typeof raw !== \"string\") return null;\n try {\n const parsed: unknown = JSON.parse(raw);\n return isRecord(parsed) ? parsed : null;\n } catch {\n return null;\n }\n}\n\n/** One column of a result row. node:sqlite types rows as `unknown`, so a\n * value that is not a row object yields no column at all. */\nfunction readColumn(row: unknown, column: string): unknown {\n return isRecord(row) ? row[column] : undefined;\n}\n\nfunction rowsToItems(rows: unknown[]): CollectionItem[] {\n return rows.map((row) => parseRow(readColumn(row, \"record\"))).filter((item): item is CollectionItem => item !== null);\n}\n\n/** node:sqlite hands back an integer column as `number`, or as `bigint` once\n * it leaves the safe-integer range — COUNT(*) can be either. */\nfunction countRecords(database: SqliteDatabase): number {\n const count = readColumn(database.prepare(\"SELECT COUNT(*) AS n FROM records\").get(), \"n\");\n if (typeof count === \"number\") return count;\n if (typeof count === \"bigint\") return Number(count);\n throw new Error(`sqlite COUNT(*) returned no numeric row count (got ${typeof count})`);\n}\n\nasync function sqliteList(absPath: string, workspaceRoot: string): Promise<CollectionItem[]> {\n return withDb<CollectionItem[]>(\n absPath,\n workspaceRoot,\n \"read\",\n () => [],\n (database) => rowsToItems(database.prepare(\"SELECT record FROM records ORDER BY id\").all()),\n );\n}\n\nasync function sqlitePage(absPath: string, primaryKey: string, opts: ListOptions, workspaceRoot: string): Promise<ListPage> {\n const emptyPage: ListPage = { items: [], total: 0, truncated: false };\n return withDb(\n absPath,\n workspaceRoot,\n \"read\",\n () => emptyPage,\n (database) => {\n const total = countRecords(database);\n const offset = Math.max(0, opts.offset ?? 0);\n const limit = opts.limit === undefined ? -1 : Math.max(0, opts.limit); // LIMIT -1 = unbounded in SQLite\n const rows = database.prepare(\"SELECT record FROM records ORDER BY id LIMIT ? OFFSET ?\").all(limit, offset);\n return { items: projectItemFields(rowsToItems(rows), opts.fields, primaryKey), total, truncated: false };\n },\n );\n}\n\nasync function sqliteRead(absPath: string, itemId: string, workspaceRoot: string): Promise<CollectionItem | null> {\n const safeId = safeRecordId(itemId);\n if (safeId === null) return null;\n return withDb<CollectionItem | null>(\n absPath,\n workspaceRoot,\n \"read\",\n () => null,\n (database) => {\n const row = database.prepare(\"SELECT record FROM records WHERE id = ?\").get(safeId);\n return parseRow(readColumn(row, \"record\"));\n },\n );\n}\n\nasync function sqliteWrite(\n absPath: string,\n itemId: string,\n item: CollectionItem,\n opts: { workspaceRoot: string; publishRoot?: string | undefined; slug?: string | undefined; refuseOverwrite?: boolean | undefined },\n): Promise<WriteItemResult> {\n const safeId = safeRecordId(itemId);\n if (safeId === null) return { kind: \"invalid-id\", itemId };\n const outcome = await withDb<WriteItemResult>(\n absPath,\n opts.workspaceRoot,\n \"write\",\n () => ({ kind: \"path-escape\", itemId: safeId }),\n (database) => {\n const payload = JSON.stringify(item);\n if (opts.refuseOverwrite) {\n // The PRIMARY KEY constraint is the race-safe create gate — the\n // sqlite twin of the file store's O_EXCL open.\n try {\n database.prepare(\"INSERT INTO records (id, record) VALUES (?, ?)\").run(safeId, payload);\n } catch (err) {\n if (isUniqueConstraintError(err)) return { kind: \"conflict\", itemId: safeId };\n throw err;\n }\n } else {\n database.prepare(\"INSERT INTO records (id, record) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET record = excluded.record\").run(safeId, payload);\n }\n return { kind: \"ok\", itemId: safeId, item };\n },\n );\n // Publish AFTER the write lands (same ordering rule as io.ts) so a live\n // subscriber that refetches always sees the new record.\n if (outcome.kind === \"ok\" && opts.slug) publishCollectionChange(collectionChangePayload({ slug: opts.slug, ids: [safeId], op: \"upsert\" }, opts.publishRoot));\n return outcome;\n}\n\nasync function sqliteDelete(\n absPath: string,\n itemId: string,\n opts: { workspaceRoot: string; publishRoot?: string | undefined; slug?: string },\n): Promise<DeleteItemResult> {\n const safeId = safeRecordId(itemId);\n if (safeId === null) return { kind: \"invalid-id\", itemId };\n // `missing` db = nothing was ever written ⇒ not-found; `refused`\n // (containment/symlink) must surface as path-escape, never as a 404.\n const outcome = await withDb<DeleteItemResult>(\n absPath,\n opts.workspaceRoot,\n \"read\",\n (reason) => (reason === \"refused\" ? { kind: \"path-escape\", itemId: safeId } : { kind: \"not-found\", itemId: safeId }),\n (database) => {\n const { changes } = database.prepare(\"DELETE FROM records WHERE id = ?\").run(safeId);\n return Number(changes) === 0 ? { kind: \"not-found\", itemId: safeId } : { kind: \"ok\", itemId: safeId };\n },\n );\n if (outcome.kind === \"ok\" && opts.slug) publishCollectionChange(collectionChangePayload({ slug: opts.slug, ids: [safeId], op: \"delete\" }, opts.publishRoot));\n return outcome;\n}\n\n/** Best-effort full WAL checkpoint so the MAIN db file alone is a\n * complete snapshot (committed pages in `<db>-wal` are folded in and the\n * WAL truncated). Used by `deleteCollection` before archiving. Returns\n * false on any failure (runtime without node:sqlite, locked db, missing\n * file) — the caller then archives the sidecar files alongside the db so\n * no committed data is lost either way. */\nexport async function checkpointSqliteDatabase(absPath: string): Promise<boolean> {\n try {\n const { DatabaseSync } = await loadSqlite();\n const database = new DatabaseSync(absPath);\n try {\n database.exec(\"PRAGMA wal_checkpoint(TRUNCATE)\");\n } finally {\n database.close();\n }\n return true;\n } catch {\n return false;\n }\n}\n\n/** A `storage: sqlite` store over `collection.storageFile`. A schema whose\n * `storageFile` failed to resolve yields a read-only EMPTY store rather\n * than a writable one — same fail-closed rule as the CSV store. */\nexport function sqliteStoreFor(collection: LoadedCollection, opts: IoOptions): CollectionStore {\n const file = collection.storageFile;\n const key = collection.schema.primaryKey;\n const slug = opts.slug ?? collection.slug;\n const root = (): string => opts.workspaceRoot ?? getWorkspaceRoot();\n // The EXPLICIT root (undefined when the caller relied on the host default) —\n // `root()` has already collapsed that distinction, and the change payload\n // must report only a root the caller actually named.\n const publishRoot = opts.workspaceRoot;\n if (file === undefined) {\n return {\n capabilities: { writable: false, nativeQuery: false, nativePaging: false },\n list: () => Promise.resolve([]),\n page: () => Promise.resolve({ items: [], total: 0, truncated: false }),\n read: () => Promise.resolve(null),\n };\n }\n return {\n capabilities: { writable: true, nativeQuery: false, nativePaging: true },\n list: () => sqliteList(file, root()),\n page: (pageOpts = {}) => sqlitePage(file, key, pageOpts, root()),\n read: (itemId: string) => sqliteRead(file, itemId, root()),\n write: (itemId: string, item: CollectionItem, writeOpts: WriteOptions = {}) =>\n sqliteWrite(file, itemId, item, { workspaceRoot: root(), publishRoot, slug, refuseOverwrite: writeOpts.refuseOverwrite }),\n delete: (itemId: string) => sqliteDelete(file, itemId, { workspaceRoot: root(), publishRoot, slug }),\n // One db file holds every record, so an event can't name a record. The\n // sidecars count as hits: sqlite writes land in `<db>-wal` first, and a\n // change that only touched the WAL is still a change.\n watch: async (onChange) =>\n closerFor(\n await watchSingleFile(\n file,\n (base, name) => name.startsWith(base),\n () => onChange({ kind: \"collection\" }),\n ),\n ),\n };\n}\n","// Storage abstraction over a collection's records — the one seam where\n// \"where do the rows come from\" is decided. Implementations, selected by\n// the schema's storage kind (`storageKindFor`) through the factory\n// registry below:\n//\n// - file store (\"file\"): the classic `<dataDir>/<itemId>.json` records\n// (io.ts), writable through the governed write paths;\n// - CSV store (\"csv\", csvStore.ts): the rows of an external `dataSource`\n// file, queried through DuckDB — READ-ONLY by definition;\n// - SQLite store (\"sqlite\", sqliteStore.ts): records in a single\n// node:sqlite database file — writable, native paging.\n//\n// Reads AND writes go through `storeFor(...)`. Writability is encoded by\n// PRESENCE: `write`/`delete` exist only on writable stores, so \"write\n// through a read-only store\" is a type error, not a runtime surprise —\n// entry points refuse with `readOnlyRefusal` when the methods are absent.\n// (`io.ts#writeItem`/`deleteItem` remain the file-store implementation and\n// the change-event choke point; only callers WITHOUT a `LoadedCollection`\n// in hand — e.g. `spawn.ts` — still call them directly.)\n//\n// BACKWARD COMPATIBILITY — read before evolving this interface.\n// This store is INTERNAL and may change shape, but two user-facing\n// contracts built on top of it are effectively FROZEN, because they are\n// consumed by LLM-authored custom-view HTML files that already live in\n// users' workspaces (`data/skills/*/views/*.html`, `feeds/*/views/*.html`).\n// Those files cannot be migrated centrally — there is no registry of them,\n// and users expect a view authored months ago to keep working:\n//\n// - the desktop view-data HTTP surface (`server/api/routes/collections.ts`:\n// GET `?fields=`/`?ids=`, PUT items, POST /query, POST /actions/<id>,\n// response shapes, error semantics) as documented in\n// `packages/core/assets/helps/custom-view.md`;\n// - the remote-view bridge (`../../remote-view/index.ts`: `__MC_VIEW`\n// protocol, `getItems` page shape `{ items, total, offset, limit }`,\n// mutate replies) as documented in\n// `packages/core/assets/helps/custom-view-remote.md`.\n//\n// Any storage-virtualization work (new backends, paging, capability\n// changes) must be invisible at those two surfaces: evolve them by\n// ADDITIVE, backward-compatible supersets only — never rename/repurpose\n// params or message types, never change existing response shapes, never\n// let a new backend alter what an existing view observes.\n\nimport { fieldText } from \"../core/fieldText\";\nimport type { CollectionItem, CollectionStorageKind } from \"../core/schema\";\nimport type { CollectionQuery } from \"../core/queryZ\";\nimport { isReadOnlySchema, storageKindFor } from \"../core/schema\";\nimport type { LoadedCollection } from \"./discoveredCollection\";\nimport { deleteItem, listItems, readItem, writeItem, type DeleteItemResult, type IoOptions, type WriteItemResult } from \"./io\";\nimport { csvList, csvRead, csvRunQuery } from \"./csvStore\";\nimport { sqliteStoreFor } from \"./sqliteStore\";\nimport { firestoreStoreFor } from \"./firestoreStore\";\nimport { pageFromFullRead, type ListOptions, type ListPage, type WriteOptions } from \"./storePage\";\nimport { closerFor, watchDirectory, watchSingleFile } from \"./watchFs\";\n\n// The pure paging/projection primitives live in storePage.ts (so backend\n// modules can share them without an import cycle); re-exported here to\n// keep the public surface where it has always been.\nexport { pageFromFullRead, projectItemFields, type ListOptions, type ListPage, type WriteOptions } from \"./storePage\";\n\nexport interface CollectionStoreCapabilities {\n readonly writable: boolean;\n /** Native aggregation engine for the structured DSL (`core/queryZ.ts`).\n * False ⇒ `query` is absent; the engine-level fallback (enrich →\n * JSONL → DuckDB, `queryRunner.ts`) answers aggregations instead. */\n readonly nativeQuery: boolean;\n /** True when `page` resolves offset/limit inside the backend. False ⇒\n * `page` is emulated (full read, then slice) — same result, no saving. */\n readonly nativePaging: boolean;\n}\n\n/** The storage contract every backend must satisfy (verified by the shared\n * contract test suite, `test/workspace/collections/test_storeContract.ts`):\n *\n * 1. STABLE ORDER — `page` walks a documented deterministic order (file\n * store: lexicographic by record id; CSV store: file row order; SQLite\n * store: `ORDER BY id`), so `offset`-paging never skips or repeats\n * records between calls. Sorting by arbitrary fields is NOT the\n * store's job.\n * 2. IDS — minting/resolving record ids is the store's job (the CSV\n * store's `id0x…` encoding stays inside it); `read` resolves every id\n * `list`/`page` returned.\n * 3. CONTAINMENT — a store never serves data from outside the workspace;\n * symlink/realpath defenses are each implementation's obligation\n * (io.ts is the reference).\n * 4. HONEST AGGREGATION — `query`, when present, is computed over the\n * WHOLE data set, never from a capped read. */\n/** What a store reports when its records changed underneath it.\n *\n * Two granularities, because backends genuinely differ: a per-record file\n * store knows WHICH record changed, while a single-artifact backend (one\n * CSV, one db file, one remote snapshot) only knows THAT something did.\n * Naming the difference here is what lets the watcher react uniformly\n * instead of branching on the backend. */\nexport type StoreChange = { readonly kind: \"item\"; readonly itemId: string } | { readonly kind: \"collection\" };\n\nexport type StoreChangeListener = (change: StoreChange) => void;\n\n/** Detaches a `watch` subscription. */\nexport type StoreUnsubscribe = () => void;\n\nexport interface CollectionStore {\n readonly capabilities: CollectionStoreCapabilities;\n /** Every record, in the store's stable order. CSV store: capped at\n * `MAX_CSV_ROWS` (see csvStore.ts). Prefer `page` in new code. */\n list: () => Promise<CollectionItem[]>;\n /** One page of records — offset/limit/projection over the stable order. */\n page: (opts?: ListOptions) => Promise<ListPage>;\n /** One record by id, or null when missing/invalid. */\n read: (itemId: string) => Promise<CollectionItem | null>;\n /** Aggregation over the WHOLE data set (the structured DSL,\n * `core/queryZ.ts`) — present only on stores with a native query\n * engine (the CSV store). Absent ⇒ use the engine-level fallback\n * (`runCollectionQuery`), never emulate ad hoc. */\n query?: ((query: CollectionQuery) => Promise<Record<string, unknown>[]>) | undefined;\n /** Present ONLY when `capabilities.writable` — absence IS the read-only\n * refusal (surface it with `readOnlyRefusal`). A successful write/delete\n * publishes a collection-change event: the store always threads the\n * collection's slug into the publish hook, so no writer can forget it. */\n write?: ((itemId: string, item: CollectionItem, opts?: WriteOptions) => Promise<WriteItemResult>) | undefined;\n delete?: ((itemId: string) => Promise<DeleteItemResult>) | undefined;\n /** Subscribe to changes made OUTSIDE this process — a file edited by the\n * agent, a CSV replaced by the user, a db written by another tool.\n *\n * Resolves to an unsubscribe function, or to `null` when the backend\n * could not arm the watch (no inotify watches left, an unreadable\n * directory). `null` is NOT the same as absent `watch`: absent means the\n * backend never reports changes and the caller should settle for its\n * periodic pass, while `null` means this attempt failed and mounting\n * should be retried. Swallowing the difference strands a collection on\n * stale data until the process restarts.\n *\n * This is the store's job because only it knows where its bytes live and\n * what its own change events look like (which paths to watch, which\n * filenames are noise, whether an atomic replace surfaces as two events).\n * Absorbing that noise — debouncing a replace into one report — belongs\n * here too. What to DO about a change (reconcile bells, sweep, publish)\n * is the watcher's policy and is deliberately NOT expressed here.\n *\n * Absent ⇒ the backend cannot report external changes; the caller falls\n * back to its periodic pass. */\n watch?: (onChange: StoreChangeListener) => Promise<StoreUnsubscribe | null>;\n}\n\n/** The file store's stable order: lexicographic by record id (codepoint\n * compare — locale-independent). `listItems` returns readdir order, which\n * is filesystem-dependent; paging needs determinism. */\nfunction sortByRecordId(items: CollectionItem[], primaryKey: string): CollectionItem[] {\n return [...items].sort((left, right) => {\n const leftId = fieldText(left[primaryKey]);\n const rightId = fieldText(right[primaryKey]);\n if (leftId < rightId) return -1;\n return leftId > rightId ? 1 : 0; // 0 on equality — a comparator that never ties breaks sort's contract\n });\n}\n\n/** True when the collection accepts UI/tool writes. A `dataSource`\n * collection is read-only: updates happen by editing/replacing the\n * data file itself. Every write entry point checks this BEFORE calling\n * `writeItem`/`deleteItem` — server-enforced, not just UI-hidden. */\nexport function collectionWritable(collection: Pick<LoadedCollection, \"schema\">): boolean {\n return !isReadOnlySchema(collection.schema);\n}\n\n/** The one-line refusal write paths surface (HTTP 405 / MCP error text). */\nexport function readOnlyRefusal(slug: string): string {\n return `collection '${slug}' is read-only (backed by an external dataSource) — update the data file itself instead`;\n}\n\n/** A `dataSource` store over `file` (CSV row order; DuckDB-native query).\n * A schema whose `dataSourceFile` failed to resolve yields a read-only\n * EMPTY store rather than falling back to the (writable) file store — a\n * half-loaded read-only collection must never become writable. */\nfunction csvStoreFor(collection: LoadedCollection, opts: IoOptions): CollectionStore {\n const file = collection.dataSourceFile;\n const key = collection.schema.primaryKey;\n const listAll = () => (file === undefined ? Promise.resolve({ items: [], truncated: false }) : csvList(file, key, opts.workspaceRoot));\n return {\n capabilities: { writable: false, nativeQuery: true, nativePaging: false },\n list: () => listAll().then((result) => result.items),\n page: (pageOpts = {}) => listAll().then((result) => pageFromFullRead(result.items, pageOpts, key, result.truncated)),\n read: (itemId: string) => (file === undefined ? Promise.resolve(null) : csvRead(file, key, itemId, opts.workspaceRoot)),\n query: (query: CollectionQuery) => (file === undefined ? Promise.resolve([]) : csvRunQuery(file, key, query, opts.workspaceRoot)),\n // One file holds every row, so an event can't name a record — only that\n // the set changed. `watchSingleFile` watches the parent dir (an atomic\n // replace swaps the inode) and debounces the 2-3 events a replace emits.\n ...(file === undefined\n ? {}\n : {\n watch: async (onChange) =>\n closerFor(\n await watchSingleFile(\n file,\n () => false,\n () => onChange({ kind: \"collection\" }),\n ),\n ),\n }),\n };\n}\n\n/** The classic file store over `<dataDir>/<itemId>.json` records. */\nfunction fileStoreFor(collection: LoadedCollection, opts: IoOptions): CollectionStore {\n const key = collection.schema.primaryKey;\n const ioOpts: IoOptions = { ...opts, slug: opts.slug ?? collection.slug };\n return {\n capabilities: { writable: true, nativeQuery: false, nativePaging: false },\n list: () => listItems(collection.dataDir, opts),\n page: async (pageOpts = {}) => pageFromFullRead(sortByRecordId(await listItems(collection.dataDir, opts), key), pageOpts, key, false),\n read: (itemId: string) => readItem(collection.dataDir, itemId, opts),\n write: (itemId: string, item: CollectionItem, writeOpts: WriteOptions = {}) =>\n writeItem(collection.dataDir, itemId, item, { ...ioOpts, refuseOverwrite: writeOpts.refuseOverwrite }),\n delete: (itemId: string) => deleteItem(collection.dataDir, itemId, ioOpts),\n // One file per record, so an event names the record that changed —\n // reported at `item` granularity, which is what lets the watcher\n // reconcile just that record instead of the whole collection. Dot-\n // prefixed names are skipped: atomic writes, OS metadata and editor swap\n // files are not records.\n watch: async (onChange) =>\n closerFor(\n await watchDirectory(\n collection.dataDir,\n (name) => name.endsWith(\".json\") && !name.startsWith(\".\"),\n (filename) => onChange(filename === null ? { kind: \"collection\" } : { kind: \"item\", itemId: filename.slice(0, -\".json\".length) }),\n ),\n ),\n };\n}\n\nexport type CollectionStoreFactory = (collection: LoadedCollection, opts: IoOptions) => CollectionStore;\n\n// The store factory registry (plans/done/refactor-storage-virtualization.md,\n// Stage 3): schema storage kind → implementation. Factories live in CORE\n// (dependency-direction rule — never plugin-registered); a new backend is\n// one factory + a `StorageZ` variant + a pass of the contract test suite.\nconst storeFactories = new Map<CollectionStorageKind, CollectionStoreFactory>([\n [\"file\", fileStoreFor],\n [\"csv\", csvStoreFor],\n [\"sqlite\", sqliteStoreFor],\n [\"firestore\", firestoreStoreFor],\n]);\n\n/** Pick the store implementation for a discovered collection via the\n * factory registry. An unknown kind cannot normally reach here (the\n * schema's `StorageZ` union gates it), so the throw is a loud invariant\n * breach, not a user-facing path. */\nexport function storeFor(collection: LoadedCollection, opts: IoOptions = {}): CollectionStore {\n const kind = storageKindFor(collection.schema);\n const factory = storeFactories.get(kind);\n if (!factory) throw new Error(`no store factory registered for storage kind '${kind}'`);\n return factory(collection, opts);\n}\n","// Pure helpers for `kind: \"mutate\"` actions (plan step ④ of\n// plans/done/collection-ontology.md): the `$params.<name>` reference syntax\n// used in a mutate action's `set` map. ONE parser shared by the schema\n// refines (`schemaZ.ts` validates that every reference names a declared\n// param) and the server executor (`server/mutate.ts` resolves them\n// against the submitted form values) — the two must agree on what a\n// reference is. Zod-free and I/O-free.\n\n/** A `set` value starting with this prefix reads the named form param\n * instead of being written literally. */\nexport const PARAM_REF_PREFIX = \"$params.\";\n\n/** The param name a `set` value references, or null when the value is a\n * literal (non-strings can never be references). A bare/empty prefix\n * (`\"$params.\"`) returns the empty string — the schema refine rejects\n * it as an undeclared param, never silently treats it as a literal. */\nexport function paramRefName(value: unknown): string | null {\n if (typeof value !== \"string\" || !value.startsWith(PARAM_REF_PREFIX)) return null;\n return value.slice(PARAM_REF_PREFIX.length);\n}\n\n/** Resolve a mutate action's `set` map against the submitted params:\n * literals pass through, `$params.<name>` reads the param value. An\n * ABSENT referenced param omits the key entirely (merge semantics —\n * the stored value survives), mirroring how the record form omits\n * empty optionals rather than writing empty strings. */\nexport function resolveMutateSet(set: Record<string, string | number | boolean>, params: Record<string, unknown>): Record<string, unknown> {\n const resolved: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(set)) {\n const ref = paramRefName(value);\n if (ref === null) {\n resolved[key] = value;\n continue;\n }\n const paramValue = params[ref];\n if (paramValue !== undefined && paramValue !== null && paramValue !== \"\") resolved[key] = paramValue;\n }\n return resolved;\n}\n","// Cross-field rules for the collection schema — the checks a single field\n// cannot make because it cannot see its siblings.\n//\n// Each one is a named predicate over a parsed-but-unvalidated schema, returning\n// true when the schema is ACCEPTABLE. They live here rather than as anonymous\n// lambdas inside `./schemaZ`'s refine chain so each rule can be unit-tested on\n// its own, and so the chain reads as a list of rule names instead of 500 lines\n// of inline logic.\n//\n// Every rule guards a misconfiguration that would otherwise fail SILENTLY at\n// runtime: a completion bell that never rings, a spawn that fans out forever, a\n// currency that mislabels amounts. That is why they are hard errors at load\n// rather than warnings.\n//\n// The type-only import of `CollectionSchemaInput` is erased at emit, so the\n// module graph stays acyclic at runtime: schemaZ → schemaRules → schema.\n\nimport { fieldTextOrNull } from \"./fieldText\";\nimport { isSafeSlug, isSafeRecordId } from \"./ids\";\nimport { paramRefName } from \"./mutateAction\";\nimport { COMPUTED_TYPES } from \"./schema\";\nimport type { CollectionSchemaInput } from \"./schemaZ\";\n\ntype Schema = CollectionSchemaInput;\ntype Fields = Schema[\"fields\"];\n\n// A field pointer names a REAL declared field only when it is an OWN key of\n// `fields`. A bare `fields[name]` reaches inherited Object.prototype members\n// (`constructor`, `__proto__`, `toString`), so an LLM-authored pointer like\n// `completionField: \"constructor\"` would resolve to a function and pass every\n// \"names a declared field\" check, then misfire silently at runtime (#2318).\nconst declaredField = (fields: Fields, name: string): Fields[string] | undefined => (Object.hasOwn(fields, name) ? fields[name] : undefined);\n\n// The calendar anchor/end fields accept either a date-only or a datetime\n// field (the latter carries the clock for the day view).\nconst isDateLike = (type: string | undefined): boolean => type === \"date\" || type === \"datetime\";\n\n// `calendarTimeField` parses a free-form time string, so it must name a\n// string-backed field — a number/enum/date column has no time-range text.\nconst isTimeStringField = (type: string | undefined): boolean => type === \"string\" || type === \"text\";\n\n// Field types that can hold a currency code string. A `currencyField`\n// pointer must resolve to one of these — pointing at a number / boolean\n// / table would never yield a usable ISO code.\nconst CODE_FIELD_TYPES = new Set([\"string\", \"text\", \"enum\"]);\n\nconst namesStoredField = (fields: Fields, name: string, primaryKey: string): boolean => {\n const target = declaredField(fields, name);\n return target !== undefined && !COMPUTED_TYPES.has(target.type) && name !== primaryKey;\n};\n\nconst hasUniqueIds = (entries: { id: string }[] | undefined): boolean =>\n entries === undefined || new Set(entries.map((entry) => entry.id)).size === entries.length;\n\n// ---------------------------------------------------------------------------\n// Storage declaration\n// ---------------------------------------------------------------------------\n\n/** Exactly one storage declaration: native records need `dataPath`, an external\n * data file needs `dataSource`, an alternative backend needs `storage`. Zero\n * (nowhere to read) and several (ambiguous which wins) are equally\n * meaningless — fail loudly at load instead of picking silently. */\nexport function declaresExactlyOneStore(schema: Schema): boolean {\n return [schema.dataPath, schema.dataSource, schema.storage].filter((declared) => declared !== undefined).length === 1;\n}\n\n// NOTE: `storage` collections support the full write machinery (`spawn` /\n// `completionField` / `triggerField` / `singleton` / `ingest` / mutate actions)\n// — spawn and the watcher reconcilers go through the CollectionStore seam, and\n// a db-file watcher drives their reconciliation (collection-watchers/watcher.ts).\n/** A `dataSource` collection is read-only by definition, so schema-level write\n * machinery can never fire: `singleton` pins CREATES, `ingest` REFILLS\n * records, `spawn` WRITES successor records. Rejecting them at validation\n * kills whole classes of writes before any runtime guard. */\nexport function dataSourceDeclaresNoWriteMachinery(schema: Schema): boolean {\n if (schema.dataSource === undefined) return true;\n return schema.singleton === undefined && schema.ingest === undefined && schema.spawn === undefined && schema.googleCalendar === undefined;\n}\n\n/** Same rule for declarative host writes: a mutate action writes the record\n * it's invoked on, which a read-only collection has no business doing. */\nexport function dataSourceDeclaresNoMutateAction(schema: Schema): boolean {\n if (schema.dataSource !== undefined) {\n return [...(schema.actions ?? []), ...(schema.collectionActions ?? [])].every((action) => action.kind !== \"mutate\");\n }\n return true;\n}\n\n// ---------------------------------------------------------------------------\n// Actions\n// ---------------------------------------------------------------------------\n\n/** Action ids must be unique so the dispatch route resolves unambiguously. */\nexport function actionIdsAreUnique(schema: Schema): boolean {\n return hasUniqueIds(schema.actions);\n}\n\n/** Collection-level action ids must likewise be unique. */\nexport function collectionActionIdsAreUnique(schema: Schema): boolean {\n return hasUniqueIds(schema.collectionActions);\n}\n\n/** A mutate action's `set` writes real STORED fields: a typo'd key would write\n * a stray value forever, a computed/projected field is never persisted, and\n * the primaryKey is the filename (renaming is not a mutation). */\nexport function mutateSetKeysNameStoredFields(schema: Schema): boolean {\n return (schema.actions ?? []).every(\n (action) => action.kind !== \"mutate\" || Object.keys(action.set).every((key) => namesStoredField(schema.fields, key, schema.primaryKey)),\n );\n}\n\n/** Every `$params.<name>` reference in `set` must name a declared param — an\n * undeclared one would silently no-op the assignment. */\nexport function mutateParamRefsAreDeclared(schema: Schema): boolean {\n return (schema.actions ?? []).every(\n (action) =>\n action.kind !== \"mutate\" ||\n Object.values(action.set).every((value) => {\n const ref = paramRefName(value);\n return ref === null || (action.params ?? {})[ref] !== undefined;\n }),\n );\n}\n\n/** A collection-level action has no record to write. */\nexport function collectionActionsAreNotMutate(schema: Schema): boolean {\n return (schema.collectionActions ?? []).every((action) => action.kind !== \"mutate\");\n}\n\n// ---------------------------------------------------------------------------\n// Singleton, currency\n// ---------------------------------------------------------------------------\n\n/** The singleton value becomes a record id (and thus a `<id>.json` filename),\n * so it must satisfy the SAME record-id rule the write path enforces —\n * otherwise the create form would lock the primary key to a value the POST\n * route then rejects, making the collection impossible to initialize. */\nexport function singletonIsAValidRecordId(schema: Schema): boolean {\n return schema.singleton === undefined || isSafeRecordId(schema.singleton);\n}\n\n// `type` is carried on both levels even though this helper never reads it:\n// without a required property these become weak types, and a field variant that\n// declares no `currencyField` would no longer be assignable.\ninterface CurrencyBearingField {\n type: string;\n currencyField?: string | undefined;\n of?: Record<string, { type: string; currencyField?: string | undefined }> | undefined;\n}\n\n// Every `currencyField` declared anywhere in the schema — top-level fields and\n// a table's `of` sub-fields. Sub-field money cells resolve currency against the\n// TOP-LEVEL record (rows carry no currency), so their pointers are validated\n// against the top-level field set too.\nfunction collectCurrencyFieldRefs(fields: Record<string, CurrencyBearingField>): string[] {\n const refs: string[] = [];\n for (const field of Object.values(fields)) {\n if (typeof field.currencyField === \"string\" && field.currencyField.length > 0) refs.push(field.currencyField);\n for (const sub of Object.values(field.of ?? {})) {\n if (typeof sub.currencyField === \"string\" && sub.currencyField.length > 0) refs.push(sub.currencyField);\n }\n }\n return refs;\n}\n\n/** A `currencyField` pointer must name a real top-level field that holds a code\n * string — a typo (`curreny`) would otherwise pass the per-field check, then\n * silently fall back to the literal / USD at render and mislabel amounts. */\nexport function currencyFieldRefsNameCodeFields(schema: Schema): boolean {\n return collectCurrencyFieldRefs(schema.fields).every((name) => CODE_FIELD_TYPES.has(declaredField(schema.fields, name)?.type ?? \"\"));\n}\n\n// ---------------------------------------------------------------------------\n// Completion tracking\n// ---------------------------------------------------------------------------\n\n/** The pair must be declared together — one without the other is meaningless:\n * the host would either never fire (no done values to compare against) or\n * never clear (no field to read).\n *\n * EXCEPTION: when `completionField` names a `flag` field, done ⇔ the flag's\n * `where` matches, so `completionDoneValues` carries no information and MUST\n * be omitted (declaring it would invite a contradictory second source of\n * truth). */\nexport function completionPairIsCoherent(schema: Schema): boolean {\n if (schema.completionField !== undefined && declaredField(schema.fields, schema.completionField)?.type === \"flag\") {\n return schema.completionDoneValues === undefined;\n }\n return (schema.completionField === undefined) === (schema.completionDoneValues === undefined);\n}\n\n/** `completionField` must name a real top-level field — a typo would silently\n * disable the notification mechanism otherwise. */\nexport function completionFieldIsDeclared(schema: Schema): boolean {\n return schema.completionField === undefined || declaredField(schema.fields, schema.completionField) !== undefined;\n}\n\n/** A flag named by `completionField` is evaluated against the RAW record — the\n * reconciler (and spawn's fallback) read items straight off disk, BEFORE any\n * `deriveAll` enrichment — so its `where` may only reference STORED fields. A\n * condition over a computed sibling would see an absent key: `ne` matches\n * vacuously, every other op reads false, and the bell would clear wrongly /\n * never. General (non-completion) flags keep the full vocabulary — the UI\n * evaluates them post-enrichment. */\nexport function completionFlagReadsOnlyStoredFields(schema: Schema): boolean {\n const spec = schema.completionField === undefined ? undefined : declaredField(schema.fields, schema.completionField);\n if (spec?.type !== \"flag\") return true;\n return spec.where.every((cond) =>\n [cond.field, ...(cond.valueFrom ? [cond.valueFrom.field] : [])].every((name) => {\n const target = declaredField(schema.fields, name);\n return target !== undefined && !COMPUTED_TYPES.has(target.type);\n }),\n );\n}\n\n// ---------------------------------------------------------------------------\n// Field pointers\n// ---------------------------------------------------------------------------\n\n/** `displayField`, like `completionField`, must name a real top-level field —\n * a typo would silently fall back to the primaryKey forever. */\nexport function displayFieldIsDeclared(schema: Schema): boolean {\n return schema.displayField === undefined || declaredField(schema.fields, schema.displayField) !== undefined;\n}\n\n/** A field's `when.field` gates its visibility against a sibling's value, so it\n * must name a real top-level field — a typo would silently keep the field\n * hidden forever (the gate never matches). */\nexport function fieldVisibilityGatesNameDeclaredFields(schema: Schema): boolean {\n return Object.values(schema.fields).every((field) => field.when === undefined || declaredField(schema.fields, field.when.field) !== undefined);\n}\n\n/** A flag's `where` reads sibling fields (both `cond.field` and a same-record\n * `valueFrom.field`), so each must name a real top-level field — a typo would\n * silently pin the flag false forever (`ne`: true forever). */\nexport function flagConditionsNameDeclaredFields(schema: Schema): boolean {\n return Object.values(schema.fields).every(\n (field) =>\n field.type !== \"flag\" ||\n field.where.every(\n (cond) =>\n declaredField(schema.fields, cond.field) !== undefined &&\n (cond.valueFrom === undefined || declaredField(schema.fields, cond.valueFrom.field) !== undefined),\n ),\n );\n}\n\n/** An `embed`'s `idField` resolves the target record id from a sibling's value,\n * so it must name a real top-level field — and one whose stored value is a\n * plain id string. Only `ref` / `string` qualify: the editor writes the picked\n * id into that field, so a non-persisted or composite type would either not\n * round-trip on save or hold no usable id. */\nexport function embedIdFieldsNameIdBearingFields(schema: Schema): boolean {\n return Object.values(schema.fields).every((field) => {\n if (field.type !== \"embed\" || field.idField === undefined) return true;\n const target = declaredField(schema.fields, field.idField);\n return target !== undefined && (target.type === \"ref\" || target.type === \"string\");\n });\n}\n\n/** The sync writes each mapped value into a declared field, and puts the Google\n * event id in the primary field — so a map key that names no field (or names\n * the primary) would silently drop data or fight the id. */\nexport function googleCalendarMapNamesStoredFields(schema: Schema): boolean {\n if (schema.googleCalendar === undefined) return true;\n return Object.keys(schema.googleCalendar.map).every((key) => namesStoredField(schema.fields, key, schema.primaryKey));\n}\n\n// ---------------------------------------------------------------------------\n// Toggle projection\n// ---------------------------------------------------------------------------\n\n/** A `toggle` field projects an `enum` field: its `field` must name a real\n * top-level enum, and `onValue` / `offValue` must be members of that enum's\n * `values` — otherwise toggling would write a value outside the closed set\n * (and never appear \"checked\"). */\nexport function togglesProjectValidEnums(schema: Schema): boolean {\n const { fields } = schema;\n for (const spec of Object.values(fields)) {\n if (spec.type !== \"toggle\") continue;\n const target = declaredField(fields, spec.field);\n if (!target || target.type !== \"enum\") return false;\n const allowed = new Set(target.values);\n if (!allowed.has(spec.onValue) || !allowed.has(spec.offValue)) return false;\n }\n return true;\n}\n\n// ---------------------------------------------------------------------------\n// Trigger\n// ---------------------------------------------------------------------------\n\n/** `triggerField` requires the completion pair: the time gate only suppresses\n * the *completion* bell until the date, and the bell still clears via\n * `completionDoneValues`. Without completion there is no bell to gate. */\nexport function triggerFieldRequiresCompletion(schema: Schema): boolean {\n return schema.triggerField === undefined || schema.completionField !== undefined;\n}\n\n/** `triggerField` must name a real `date` field — the gate parses its value as\n * `YYYY-MM-DD`; any other type can't be compared to the clock. */\nexport function triggerFieldIsADateField(schema: Schema): boolean {\n return schema.triggerField === undefined || declaredField(schema.fields, schema.triggerField)?.type === \"date\";\n}\n\n/** `triggerLeadDays` only means something relative to a trigger date. */\nexport function triggerLeadDaysRequiresTriggerField(schema: Schema): boolean {\n return schema.triggerLeadDays === undefined || schema.triggerField !== undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Spawn\n// ---------------------------------------------------------------------------\n\n/** `spawn` advances `triggerField` to compute the successor's trigger date, so\n * the schema must declare one. */\nexport function spawnRequiresTriggerField(schema: Schema): boolean {\n return schema.spawn === undefined || schema.triggerField !== undefined;\n}\n\n/** `spawn.when.field` must name a real top-level field — a typo would silently\n * never match. */\nexport function spawnWhenFieldIsDeclared(schema: Schema): boolean {\n return schema.spawn?.when === undefined || declaredField(schema.fields, schema.spawn.when.field) !== undefined;\n}\n\n/** Every `spawn.carry` entry must name a real top-level field — a typo would\n * silently never copy. */\nexport function spawnCarryEntriesAreDeclared(schema: Schema): boolean {\n return (schema.spawn?.carry ?? []).every((name) => declaredField(schema.fields, name) !== undefined);\n}\n\n/** A successor must NOT be born already matching its own spawn predicate — it\n * would re-spawn on its first reconcile, fanning out into an unbounded chain\n * of records. The predicate field/values are `spawn.when` when given, else the\n * completion-done pair. The successor's value for that field is `set[field]`\n * if set, else the carried source value (which matched, by definition, when\n * the spawn fired) if carried, else absent (safe). */\nexport function spawnSuccessorStartsInert(schema: Schema): boolean {\n const { spawn } = schema;\n if (!spawn) return true;\n const field = spawn.when?.field ?? schema.completionField;\n const values = spawn.when?.in ?? schema.completionDoneValues;\n if (!field || !values) return true; // predicate not evaluable — other rules cover this\n if (spawn.set && Object.prototype.hasOwnProperty.call(spawn.set, field)) {\n return !values.includes(String(spawn.set[field])); // `set` wins over `carry`\n }\n return !(spawn.carry ?? []).includes(field); // carried ⇒ inherits the matching value\n}\n\n/** `spawnSuccessorStartsInert` cannot see through a flag's `where` (the\n * predicate would need full record evaluation against `set`/`carry`). So a\n * schema whose completion is flag-form may only spawn with an explicit\n * `spawn.when` — which that check CAN evaluate. */\nexport function flagCompletionSpawnDeclaresWhen(schema: Schema): boolean {\n return schema.spawn === undefined || schema.spawn.when !== undefined || declaredField(schema.fields, schema.completionField ?? \"\")?.type !== \"flag\";\n}\n\n// Resolve the field-driven arm of `spawn.every`, or null when spawn is absent\n// or its `every` is the literal arm. Lets each rule below short-circuit\n// (return valid) without re-checking the discriminant.\nfunction fieldDrivenSpawnEvery(schema: Schema) {\n const every = schema.spawn?.every;\n if (!every || !(\"fromField\" in every)) return null;\n return every;\n}\n\n/** §4.1 — `fromField` must name a real top-level `enum` field. The `map` keys\n * are only meaningful against a closed value set, and the field renders as a\n * form `<select>`; a non-enum target has no finite values to validate. */\nexport function fieldDrivenFromFieldIsEnum(schema: Schema): boolean {\n const driven = fieldDrivenSpawnEvery(schema);\n if (!driven) return true;\n return declaredField(schema.fields, driven.fromField)?.type === \"enum\";\n}\n\n/** §4.2 — `map` keys must EXACTLY cover the enum's `values` (no missing keys —\n * a record could pick an unmapped frequency and silently stall; no extra keys\n * — a stale map outliving an enum edit). */\nexport function fieldDrivenMapCoversValues(schema: Schema): boolean {\n const driven = fieldDrivenSpawnEvery(schema);\n if (!driven) return true;\n const target = declaredField(schema.fields, driven.fromField);\n if (target?.type !== \"enum\") return true; // §4.1 reports the type error\n const values = new Set<string>(target.values);\n const keys = Object.keys(driven.map);\n return keys.length === values.size && keys.every((key) => values.has(key));\n}\n\n/** §4.5 — `fromField` must reach the successor (via `carry` or `set`);\n * otherwise the successor loses its frequency and the NEXT spawn along the\n * chain can't resolve an interval, silently halting the recurrence.\n *\n * `set` writes a FIXED value, so it must itself be a key of `map` (else the\n * successor is born with an unresolvable driver and `resolveEvery` skips it —\n * the exact silent-halt §4.5 exists to prevent). `carry` copies the source's\n * own value, which — for a record that matched the spawn — is one of the\n * enum's values, all of which `map` covers by §4.2; so a carried driver is\n * always resolvable and needs no value check here. */\nexport function fieldDrivenFromFieldCarried(schema: Schema): boolean {\n const driven = fieldDrivenSpawnEvery(schema);\n if (!driven) return true;\n const { carry, set } = schema.spawn ?? {};\n if (set && Object.prototype.hasOwnProperty.call(set, driven.fromField)) {\n const raw = set[driven.fromField];\n if (raw === undefined || raw === null || raw === \"\") return false;\n const key = fieldTextOrNull(raw);\n return key !== null && Object.prototype.hasOwnProperty.call(driven.map, key);\n }\n return (carry ?? []).includes(driven.fromField);\n}\n\n// ---------------------------------------------------------------------------\n// Calendar\n// ---------------------------------------------------------------------------\n\n/** `calendarField` must name a real `date`/`datetime` field — the calendar view\n * parses its value to place records on the month grid (a `datetime` anchor\n * also carries the clock for the day view). */\nexport function calendarFieldIsDateLike(schema: Schema): boolean {\n return schema.calendarField === undefined || isDateLike(declaredField(schema.fields, schema.calendarField)?.type);\n}\n\n/** `calendarEndField` marks the end of a multi-day span, so it only means\n * something alongside a start anchor. */\nexport function calendarEndFieldRequiresCalendarField(schema: Schema): boolean {\n return schema.calendarEndField === undefined || schema.calendarField !== undefined;\n}\n\n/** `calendarEndField` must also name a real `date`/`datetime` field — same parse. */\nexport function calendarEndFieldIsDateLike(schema: Schema): boolean {\n return schema.calendarEndField === undefined || isDateLike(declaredField(schema.fields, schema.calendarEndField)?.type);\n}\n\n/** `calendarTimeField` places records on the day view, so it only means\n * something alongside a start anchor. */\nexport function calendarTimeFieldRequiresCalendarField(schema: Schema): boolean {\n return schema.calendarTimeField === undefined || schema.calendarField !== undefined;\n}\n\n/** `calendarTimeField` must name a real top-level field (a free-form time\n * string the day view parses). */\nexport function calendarTimeFieldIsDeclared(schema: Schema): boolean {\n return schema.calendarTimeField === undefined || declaredField(schema.fields, schema.calendarTimeField) !== undefined;\n}\n\n/** …and that field must be string-backed — the day view parses its value as a\n * time string, so a number/enum/date column can't drive it. */\nexport function calendarTimeFieldIsStringBacked(schema: Schema): boolean {\n return schema.calendarTimeField === undefined || isTimeStringField(declaredField(schema.fields, schema.calendarTimeField)?.type);\n}\n\n/** `kanbanField` must name a real `enum` field — the board groups records into\n * one column per declared enum value; any other type has no closed set of\n * columns to group by. */\nexport function kanbanFieldIsAnEnum(schema: Schema): boolean {\n return schema.kanbanField === undefined || declaredField(schema.fields, schema.kanbanField)?.type === \"enum\";\n}\n\n// ---------------------------------------------------------------------------\n// notifyWhen, custom views\n// ---------------------------------------------------------------------------\n\n/** `notifyWhen` narrows the completion bell, so it only means something with\n * completion tracking. */\nexport function notifyWhenRequiresCompletion(schema: Schema): boolean {\n return schema.notifyWhen === undefined || schema.completionField !== undefined;\n}\n\n/** `notifyWhen.field` must name a real top-level field. */\nexport function notifyWhenFieldIsDeclared(schema: Schema): boolean {\n return schema.notifyWhen === undefined || declaredField(schema.fields, schema.notifyWhen.field) !== undefined;\n}\n\n/** Every custom view `id` must be a valid slug — it doubles as the view-mode\n * selector key (`custom:<id>`) and the capability-token clamp key, both of\n * which expect a path-safe token. */\nexport function viewIdsAreSlugs(schema: Schema): boolean {\n return schema.views === undefined || schema.views.every((view) => isSafeSlug(view.id));\n}\n\n/** Custom view ids must be unique so the selector + token clamp resolve\n * unambiguously. */\nexport function viewIdsAreUnique(schema: Schema): boolean {\n return hasUniqueIds(schema.views);\n}\n","// The zod SINGLE SOURCE OF TRUTH for the collection schema contract.\n//\n// Every TypeScript type in `./schema` is derived from these definitions via\n// `z.infer` (type-only imports, erased at emit) — there is no hand-written\n// mirror to drift. Field specs are a DISCRIMINATED UNION on `type` (and\n// `ingest` on `kind`): each variant declares exactly the keys it owns, so an\n// unknown key is stripped per-variant, a new field type is a new union\n// member (not another optional key + refine on a flat bag), and error\n// messages name the variant that failed.\n//\n// This module is ISOMORPHIC — zod plus the pure predicates in `./ids` /\n// `./templatePath`, no node built-ins — but it is deliberately NOT exported\n// through the browser barrel (`../index`): browser code imports the derived\n// TYPES from `./schema` (type-only ⇒ no zod in the bundle) and the server\n// imports the validators through `../server` (discovery re-exports\n// `CollectionSchemaZ`). Runtime imports here point only at `./schema`'s\n// consts, so the module graph stays acyclic: schemaZ → schema.\n\nimport { isRecord, isUnknownArray } from \"@mulmoclaude/common\";\nimport { z } from \"zod\";\nimport { isSafeSlug } from \"./ids\";\nimport { isSafeActionTemplatePath, isSafeCustomViewI18nPath, isSafeCustomViewPath } from \"./templatePath\";\nimport { INGEST_KINDS, AGENT_INGEST_KIND, FEED_SCHEDULES } from \"./schema\";\nimport {\n actionIdsAreUnique,\n calendarEndFieldIsDateLike,\n calendarEndFieldRequiresCalendarField,\n calendarFieldIsDateLike,\n calendarTimeFieldIsDeclared,\n calendarTimeFieldIsStringBacked,\n calendarTimeFieldRequiresCalendarField,\n collectionActionIdsAreUnique,\n collectionActionsAreNotMutate,\n completionFieldIsDeclared,\n completionFlagReadsOnlyStoredFields,\n completionPairIsCoherent,\n currencyFieldRefsNameCodeFields,\n dataSourceDeclaresNoMutateAction,\n dataSourceDeclaresNoWriteMachinery,\n declaresExactlyOneStore,\n displayFieldIsDeclared,\n embedIdFieldsNameIdBearingFields,\n fieldDrivenFromFieldCarried,\n fieldDrivenFromFieldIsEnum,\n fieldDrivenMapCoversValues,\n fieldVisibilityGatesNameDeclaredFields,\n flagCompletionSpawnDeclaresWhen,\n flagConditionsNameDeclaredFields,\n googleCalendarMapNamesStoredFields,\n kanbanFieldIsAnEnum,\n mutateParamRefsAreDeclared,\n mutateSetKeysNameStoredFields,\n notifyWhenFieldIsDeclared,\n notifyWhenRequiresCompletion,\n singletonIsAValidRecordId,\n spawnCarryEntriesAreDeclared,\n spawnRequiresTriggerField,\n spawnSuccessorStartsInert,\n spawnWhenFieldIsDeclared,\n togglesProjectValidEnums,\n triggerFieldIsADateField,\n triggerFieldRequiresCompletion,\n triggerLeadDaysRequiresTriggerField,\n viewIdsAreSlugs,\n viewIdsAreUnique,\n} from \"./schemaRules\";\n\n// ---------------------------------------------------------------------------\n// Shared predicate shapes\n// ---------------------------------------------------------------------------\n\n/** Optional visibility predicate shared by actions and fields: the target\n * shows only when the open record's `field` (stringified) is one of `in`.\n * Domain-free — `field` is any non-empty key, `in` a non-empty array of\n * non-empty values; the host never interprets the meaning.\n *\n * `trim().min(1)` rather than bare `min(1)` so a whitespace-only string\n * (\" \") fails validation — otherwise the cell formatter / dropdown would\n * render visual blanks that look like missing data. Applied consistently to\n * every \"non-empty string\" slot in this file (CodeRabbit PR #1497). */\nexport const WhenZ = z.object({\n field: z.string().trim().min(1),\n in: z.array(z.string().trim().min(1)).min(1),\n});\n\n// `where` — a richer AND-of-conditions predicate than the single-field\n// `WhenZ` above: typed comparison ops (`eq/ne/in/contains/gt/gte/lt/lte`)\n// evaluated by `./where`'s `matchesWhere`. Consumed by `flag` fields\n// (below) and `dynamicIcon` (further down). Defined here, ahead of the\n// field specs, so `FlagFieldZ` can reference it.\n//\n// A condition's comparison value is either a literal `value` or a\n// `valueFrom` reference to another record's field (e.g. a `_config`\n// singleton's `defaultCity`, resolved at compute time against the source\n// collection's own records — see the server's `dynamicIcon.ts`\n// `recordsById`). Exactly one of the two is required: neither (nothing to\n// compare against) and both (ambiguous which wins) are equally meaningless.\n// `record` omitted → the SAME record being matched (field-to-field compare,\n// e.g. `spent > budget`); set → another record by primaryKey (e.g. `_config`).\nexport const ValueRefZ = z.object({\n record: z.string().trim().min(1).optional(),\n field: z.string().trim().min(1),\n});\nexport const WhereCondZ = z\n .object({\n field: z.string().trim().min(1),\n op: z.enum([\"eq\", \"ne\", \"in\", \"gt\", \"gte\", \"lt\", \"lte\", \"contains\"]),\n value: z.union([z.string(), z.array(z.string())]).optional(),\n valueFrom: ValueRefZ.optional(),\n })\n .refine((cond) => (cond.value !== undefined) !== (cond.valueFrom !== undefined), {\n message: \"a where condition must declare exactly one of `value` (a literal) or `valueFrom` (a reference to another record's field), never both or neither\",\n path: [\"value\"],\n })\n .refine((cond) => cond.value === undefined || (cond.op === \"in\") === Array.isArray(cond.value), {\n message: \"`in` requires an array `value` (the allowed set); every other op requires a single string `value`\",\n path: [\"value\"],\n });\nexport const WhereZ = z.array(WhereCondZ);\n\n// ---------------------------------------------------------------------------\n// Field specs — a discriminated union on `type`\n// ---------------------------------------------------------------------------\n\n// Keys every field variant carries. `when` gates visibility (list cell,\n// edit form, detail view — purely presentational, a hidden field's stored\n// value is never cleared; only honoured on top-level fields). `primary`\n// marks the field whose value is the record's filename (exactly one per\n// schema — enforced by `acceptParsedSchema`, not here). The referenced\n// `when.field` is validated to be a real top-level field by a schema-level\n// refine below (a field can't see its siblings here).\nconst fieldBase = {\n label: z.string().min(1),\n primary: z.boolean().optional(),\n required: z.boolean().optional(),\n when: WhenZ.optional(),\n};\n\n// A field that renders as money must declare where its currency comes from —\n// otherwise the formatter silently falls back to USD and mislabels non-USD\n// amounts. Two ways to satisfy it: a literal `currency` (an ISO 4217 code,\n// fixed for every record) or a `currencyField` naming a sibling record field\n// that holds the code (per-record, e.g. an invoice's `currency` enum; resolved\n// against the TOP-LEVEL record even for money sub-fields inside a table). At\n// least one is required. The stored value is always a plain decimal number;\n// currency is presentation only.\nconst currencyKeys = {\n currency: z.string().trim().min(1).optional(),\n currencyField: z.string().trim().min(1).optional(),\n};\nconst hasCurrencySource = (spec: { currency?: string | undefined; currencyField?: string | undefined }): boolean =>\n spec.currency !== undefined || spec.currencyField !== undefined;\nconst currencyMessage = {\n message:\n \"fields that render as money (type 'money', or 'derived' with display 'money') must declare either a literal `currency` (ISO 4217 code, e.g. 'USD', 'JPY') or a `currencyField` naming the record field that holds the code\",\n path: [\"currency\"],\n};\n\nconst slugMessage = (key: string) => ({\n message: `\\`${key}\\` must be a valid collection slug (alphanumeric / hyphen / underscore, no path separators)`,\n path: [key],\n});\n\n/** The plain scalar field types. Stored and edited as primitive values; no\n * variant-specific keys.\n * - `image`: a workspace-relative image path (e.g. a `data/attachments/...`\n * upload); rendered as an <img> in the detail view (not the list table —\n * a per-row fetch is too expensive at scale). Stored as a plain string.\n * - `file`: a workspace-relative file path as a plain string (e.g. an\n * `artifacts/html/<name>.html` app). Rendered as a clickable link in both\n * the list table and the detail view: HTML / SVG artifacts open their\n * rendered form in a new tab; any other path opens in the File Explorer. */\nconst ScalarFieldZ = z.object({\n type: z.enum([\"string\", \"text\", \"email\", \"number\", \"date\", \"datetime\", \"boolean\", \"markdown\", \"image\", \"file\"]),\n ...fieldBase,\n});\n\n/** A link to another collection: the record stores the target item's\n * primary-key slug and the host renders a clickable link + dropdown picker.\n * `to` must be a real slug (not `../foo`, not `mc-clients/extra` — see\n * Codex P2 on PR #1495); whether the target collection exists resolves\n * fail-soft at render time, never here. */\nconst RefFieldZ = z\n .object({\n type: z.literal(\"ref\"),\n ...fieldBase,\n to: z.string().min(1),\n })\n .refine((spec) => isSafeSlug(spec.to), slugMessage(\"to\"));\n\n/** A money amount. See `currencyKeys` for the currency-source contract. */\nconst MoneyFieldZ = z\n .object({\n type: z.literal(\"money\"),\n ...fieldBase,\n ...currencyKeys,\n })\n .refine(hasCurrencySource, currencyMessage);\n\n/** A closed set of allowed string values. The form renders a `<select>`\n * populated from `values`; storage is a plain string.\n *\n * `default` pre-fills a NEW record — the Add form starts on it, and a\n * `putItems` row in `create` mode that omits the field gets it. Never applied\n * on `merge` / `upsert`: those edit a record that already answered this.\n *\n * Membership in `values` is NOT checked here on purpose. This schema is what\n * discovery parses on every load, and a rejection there drops the whole\n * collection out of the index (`discovery.ts`) — too steep a price for a\n * cosmetic key, and the key was silently ignored before #2839, so a stale one\n * may already sit in someone's file. `putSchema` refuses a non-member instead,\n * where the author is present to read the reason. */\nconst EnumFieldZ = z.object({\n type: z.literal(\"enum\"),\n ...fieldBase,\n values: z.array(z.string().trim().min(1)).min(1),\n default: z.string().trim().min(1).optional(),\n});\n\n// Sub-fields inside a `table.of` map: the regular field types minus `table`\n// (no nested tables) and `derived` (no computed columns inside a table —\n// would need the evaluator to walk the row context, defer until a real need\n// surfaces). Also no `when` / `primary` — rows have neither visibility\n// gating nor filenames.\nconst subFieldBase = {\n label: z.string().min(1),\n required: z.boolean().optional(),\n};\nconst SubScalarFieldZ = z.object({\n type: z.enum([\"string\", \"text\", \"email\", \"number\", \"date\", \"datetime\", \"boolean\", \"markdown\"]),\n ...subFieldBase,\n});\nconst SubRefFieldZ = z.object({ type: z.literal(\"ref\"), ...subFieldBase, to: z.string().min(1) }).refine((spec) => isSafeSlug(spec.to), slugMessage(\"to\"));\nconst SubMoneyFieldZ = z.object({ type: z.literal(\"money\"), ...subFieldBase, ...currencyKeys }).refine(hasCurrencySource, currencyMessage);\nconst SubEnumFieldZ = z.object({ type: z.literal(\"enum\"), ...subFieldBase, values: z.array(z.string().trim().min(1)).min(1) });\n\nexport const SubFieldSpecZ = z.discriminatedUnion(\"type\", [SubScalarFieldZ, SubRefFieldZ, SubMoneyFieldZ, SubEnumFieldZ]);\n\n/** A flat sub-table: each row is a record of `of`'s sub-schema (insertion\n * order = column order). v0 disallows nested tables and derived columns to\n * keep the editor + evaluator simple. */\nconst TableFieldZ = z\n .object({\n type: z.literal(\"table\"),\n ...fieldBase,\n of: z.record(z.string(), SubFieldSpecZ),\n })\n .refine((spec) => Object.keys(spec.of).length > 0, {\n message: \"fields with type 'table' must declare a non-empty `of` (sub-schema for each row)\",\n path: [\"of\"],\n });\n\n/** A computed scalar: `formula` is a tiny expression evaluated against the\n * record — `+ - * /`, parens, identifier refs to top-level fields,\n * `sum(tableField[].col)`, and `sum(tableField[].col * tableField[].col)`\n * (see `./derivedFormula`). `display` picks the inner type the value renders\n * as (default `\"number\"`) — restricted to the non-composite display targets,\n * since a derived value is a scalar. Never stored; computed by `deriveAll`\n * on both server and client. */\nconst DerivedFieldZ = z\n .object({\n type: z.literal(\"derived\"),\n ...fieldBase,\n formula: z.string().trim().min(1),\n display: z.enum([\"string\", \"number\", \"money\", \"date\"]).optional(),\n ...currencyKeys,\n })\n .refine((spec) => spec.display !== \"money\" || hasCurrencySource(spec), currencyMessage);\n\n/** Pulls a record from another collection into the read-only detail view.\n * Display-only — nothing is stored on this record, so it never appears in\n * the list table or the edit form. Must declare a valid `to` slug (same\n * path-traversal guard as `ref`) AND exactly one of `id` (a fixed target\n * record, e.g. `me` for the singleton profile — same for every record) or\n * `idField` (a sibling top-level field naming the per-record target, e.g.\n * an invoice's `issuerId` selecting which profile to embed as the bill-from\n * block; an absent/empty value resolves fail-soft to \"no record\"). The\n * `idField` target is validated to be a real `ref`/`string` field by a\n * schema-level refine below. */\nconst EmbedFieldZ = z\n .object({\n type: z.literal(\"embed\"),\n ...fieldBase,\n to: z.string().min(1),\n id: z.string().trim().min(1).optional(),\n idField: z.string().trim().min(1).optional(),\n })\n .refine((spec) => isSafeSlug(spec.to) && (spec.id !== undefined) !== (spec.idField !== undefined), {\n message:\n \"fields with type 'embed' must declare a `to` (valid collection slug) and exactly one of `id` (a fixed record's primary key) or `idField` (a sibling field naming the per-record target)\",\n path: [\"id\"],\n });\n\n/** Display-only REVERSE refs (plan step ② of plans/done/collection-ontology.md):\n * a read-only sub-table of the records in collection `from` whose `via`\n * ref field stores THIS record's primary key. Stores nothing (joins\n * `COMPUTED_TYPES`); resolution is shared server/client via\n * `core/backlinks.ts`. `display` names the `from` columns to show;\n * `filter` (the standard `when` shape, matched against each SOURCE\n * record) narrows the rows. Validation is shape-only, like `embed`:\n * `from` must be a safe slug, but whether it exists — and whether `via` /\n * `display` name real fields there — resolves fail-soft at render\n * (empty sub-table). Do NOT add cross-schema existence checks here. */\nconst BacklinksFieldZ = z\n .object({\n type: z.literal(\"backlinks\"),\n ...fieldBase,\n from: z.string().min(1),\n via: z.string().trim().min(1),\n display: z.array(z.string().trim().min(1)).min(1),\n filter: WhenZ.optional(),\n })\n .refine((spec) => isSafeSlug(spec.from), slugMessage(\"from\"));\n\n/** A cross-collection AGGREGATE over a backlink relation (plan step ⑤ of\n * plans/done/collection-ontology.md): a computed number — never stored — that\n * sums a source column (or counts rows) over the records in `from` whose\n * `via` ref points at this record. Same `from`/`via`/`filter` vocabulary\n * and reverse-loading machinery as `backlinks`; resolution shared\n * server/client via `core/backlinks.ts`'s `rollupValue`. Deliberately a\n * STRUCTURED field, not `sumOver(...)` formula syntax — the derived\n * evaluator's no-string-literals boundary stays untouched — and\n * deliberately just `sum` | `count`. Rollups resolve BEFORE the formula\n * pass, so a sibling `derived` formula may read them as identifiers\n * (`played = homePlayed + awayPlayed`). Fail-soft: an unresolvable\n * `from` renders em-dash; an empty match set is a real 0. */\nconst RollupFieldZ = z\n .object({\n type: z.literal(\"rollup\"),\n ...fieldBase,\n from: z.string().min(1),\n via: z.string().trim().min(1),\n op: z.enum([\"sum\", \"count\"]),\n column: z.string().trim().min(1).optional(),\n filter: WhenZ.optional(),\n })\n .refine((spec) => isSafeSlug(spec.from), slugMessage(\"from\"))\n .refine((spec) => (spec.op === \"sum\") === (spec.column !== undefined), {\n message: 'a rollup\\'s `column` names the source column to aggregate: required for op \"sum\", meaningless for op \"count\"',\n path: [\"column\"],\n });\n\n/** A checkbox that is a pure PROJECTION of an `enum` field — it stores\n * nothing of its own. Checked when the enum named by `field` equals\n * `onValue`; toggling writes `onValue` / `offValue` back to that enum\n * field. Lets a \"done\" checkbox front a kanban `status` field with the enum\n * as the single source of truth (no separate stored boolean to keep in\n * sync). `field` / `onValue` / `offValue` are validated against the target\n * enum's `values` by a schema-level refine below. */\nconst ToggleFieldZ = z.object({\n type: z.literal(\"toggle\"),\n ...fieldBase,\n field: z.string().trim().min(1),\n onValue: z.string().trim().min(1),\n offValue: z.string().trim().min(1),\n});\n\n/** A computed boolean: the record matched against a `where` predicate\n * (the same AND-of-conditions shape `dynamicIcon` uses; evaluated by\n * `./where`'s `matchesWhere`). Generic state summaries — isDone,\n * isPassed, isQualified — become declared fields, so every\n * field-driven mechanism (table cells, detail view, completion via\n * `completionField`) picks them up without special cases. Never\n * stored; computed by `deriveAll` on both server and client in the\n * same saturation loop as `derived`, so a flag may read derived /\n * rollup values — and other flags (`matchesWhere` stringifies, so\n * `eq \"true\"` composes). Deliberately a STRUCTURED field, not formula\n * syntax — the derived evaluator's no-string-literals boundary stays\n * untouched (same reasoning as `rollup`). Cross-record\n * `valueFrom.record` is rejected here: per-record evaluation has no\n * `recordsById`, so it could only ever silently never-match.\n * `where` condition fields are validated to exist by a schema-level\n * refine below (a field can't see its siblings). */\nconst FlagFieldZ = z\n .object({\n type: z.literal(\"flag\"),\n ...fieldBase,\n where: WhereZ.min(1),\n })\n .refine((spec) => spec.where.every((cond) => cond.valueFrom?.record === undefined), {\n message:\n \"a flag's `where` cannot use `valueFrom.record` (cross-record references are unresolvable in per-record evaluation); use a literal `value` or a same-record `valueFrom` (field-to-field)\",\n path: [\"where\"],\n });\n\nexport const FieldSpecZ = z.discriminatedUnion(\"type\", [\n ScalarFieldZ,\n RefFieldZ,\n MoneyFieldZ,\n EnumFieldZ,\n TableFieldZ,\n DerivedFieldZ,\n EmbedFieldZ,\n BacklinksFieldZ,\n RollupFieldZ,\n ToggleFieldZ,\n FlagFieldZ,\n]);\n\n// ---------------------------------------------------------------------------\n// Actions & custom views\n// ---------------------------------------------------------------------------\n\n// Keys every action variant carries.\nconst actionBase = {\n id: z.string().trim().min(1),\n label: z.string().trim().min(1),\n icon: z.string().trim().min(1).optional(),\n};\n\n/** The LLM-seeded action kinds — same shape, different visibility:\n * - `\"chat\"` — start a new VISIBLE chat in `role` with the templated\n * seed prompt (judgment work: drafting, planning, conversation).\n * - `\"agent\"` — dispatch a HIDDEN worker (origin `system`) with the SAME\n * seed; it edits records via manageCollection and finishes silently\n * (mechanical enrichment: refresh a price, fetch metadata). Spinner\n * while running, deduped failure bell on error — see\n * server/api/routes/collectionAgentActions.ts. */\nconst SeededActionZ = z.object({\n kind: z.enum([\"chat\", \"agent\"]),\n ...actionBase,\n role: z.string().trim().min(1),\n template: z\n .string()\n .trim()\n .min(1)\n .refine(isSafeActionTemplatePath, \"must be a safe path under `templates/` (e.g. `templates/invoice.md`; no `..`, no leading `/`, no backslash)\"),\n when: WhenZ.optional(),\n});\n\n/** `kind: \"mutate\"` — a declarative, HOST-executed write; no LLM, no\n * tokens (plan step ④ of plans/done/collection-ontology.md). Clicking the\n * button (after an optional `params` mini-form) merges `set` into the\n * record: values are literals or `$params.<name>` references. `require`\n * is the state gate — the standard `when` shape, both the visibility\n * rule AND the server-side authorization rule, exactly like `when` on\n * the seeded kinds. `params` reuses the table sub-field DSL, and the\n * form is validated by the SAME compiled record checks `putItems` uses\n * (`recordFieldProblem`), not a third mechanism. Record-level only —\n * a collection-level mutate has no record to write (schema refine\n * below). Merge semantics make half-states unconstructible THROUGH\n * THIS PATH; the raw file stays editable by design (lint, not lock). */\nconst MutateActionZ = z\n .object({\n kind: z.literal(\"mutate\"),\n ...actionBase,\n require: WhenZ.optional(),\n params: z.record(z.string().trim().min(1), SubFieldSpecZ).optional(),\n set: z.record(z.string().trim().min(1), z.union([z.string(), z.number(), z.boolean()])),\n })\n .refine((spec) => Object.keys(spec.set).length > 0, {\n message: \"a mutate action's `set` must name at least one field to write\",\n path: [\"set\"],\n });\n\n/** A schema-declared record action, rendered as a button in the read-only\n * detail view. Domain-free: the host validates the shape; the meaning\n * (role + template prose, or the declarative `set`) is data. A\n * discriminated union on `kind` — each kind declares only its own keys. */\nexport const ActionSpecZ = z.discriminatedUnion(\"kind\", [SeededActionZ, MutateActionZ]);\n\n/** A custom (LLM-authored) HTML view registration. Domain-free: the host\n * validates the shape; the view's behaviour lives in the HTML file. `file`\n * is constrained to `views/*.html` (path-safe) so the view-file reader can\n * never reach the data folder or the schema/template files. `id` is\n * validated to be a real slug + unique by schema-level refines below. */\nexport const CustomViewZ = z.object({\n id: z.string().trim().min(1),\n label: z.string().trim().min(1),\n icon: z.string().trim().min(1).optional(),\n // Where the view runs. Absent ⇒ \"desktop\" (the sandboxed iframe over the\n // token/dataUrl contract), so every pre-existing view keeps its behavior.\n // \"mobile\" ⇒ served to the phone remote via getRemoteView (postMessage\n // contract, @mulmoclaude/core/remote-view) and phone-frame-previewed on\n // desktop. See plans/done/feat-remote-custom-view.md.\n target: z.enum([\"desktop\", \"mobile\"]).optional(),\n file: z\n .string()\n .trim()\n .min(1)\n .refine(isSafeCustomViewPath, \"must be a safe path under `views/` ending in `.html` (e.g. `views/year.html`; no `..`, no leading `/`, no backslash)\"),\n // A JSON translation dictionary co-located with the view (shape mirrors\n // vue-i18n locale messages; the host injects only the active locale's flat\n // string map into the iframe — see `CollectionCustomView.i18n`'s docs in\n // ./schema's source history / docs/developer.md).\n i18n: z\n .string()\n .trim()\n .min(1)\n .refine(\n isSafeCustomViewI18nPath,\n \"must be a safe path under `views/` ending in `.i18n.json` (e.g. `views/year.i18n.json`; no `..`, no leading `/`, no backslash)\",\n )\n .optional(),\n // What the view may do with the data endpoint. Defaults to [\"read\"] (least\n // privilege); the mint endpoint clamps any requested caps to this. There is\n // deliberately no \"delete\" — a view can never do more than the agent's own\n // manageCollection tool.\n capabilities: z.array(z.enum([\"read\", \"write\"])).optional(),\n // Mobile-only write policy (plans/done/feat-remote-writable-view.md). Default-deny:\n // a `target: \"mobile\"` view may patch ONLY these fields via\n // `__MC_VIEW.updateItem`, and may delete only when `allowDelete` is true. The\n // host re-derives + enforces both on every mutate — never trusting the client.\n // Ignored for desktop views (they use the token-scoped `capabilities` above).\n editableFields: z.array(z.string().trim().min(1)).optional(),\n allowDelete: z.boolean().optional(),\n // Mobile-only image inlining (plans/done/feat-remote-view-images.md). A\n // `target: \"mobile\"` view can't reach the host's localhost, so an `image`-type\n // field's workspace path is unrenderable on the phone; listing it here makes\n // the host inline it as a downscaled `data:` URL thumbnail in getItems pages.\n // Opt-in (absent ⇒ none), projection- and budget-bounded host-side. Ignored\n // for desktop views (they resolve via /api/files/raw).\n imageFields: z.array(z.string().trim().min(1)).optional(),\n imageMaxEdge: z.number().int().min(1).optional(),\n});\n\n// ---------------------------------------------------------------------------\n// Spawn (host-driven recurrence)\n// ---------------------------------------------------------------------------\n\n/** Recurrence advance for `spawn.every`. `interval` is a positive integer\n * count of `unit`s (`interval: 3` + `unit: \"month\"` = quarterly);\n * `dayOfMonth` (month/year only) is the CANONICAL day-of-month anchor\n * (1-31, read from the rule and clamped per-month at compute time so \"31st\n * of every month\" never drifts) or the `\"last\"` sentinel for end-of-month.\n * `.strict()` so the union below cleanly rejects an object carrying BOTH\n * `unit` and `fromField` (it fails this arm on the unknown `fromField`). */\nexport const EveryLiteralZ = z\n .object({\n unit: z.enum([\"day\", \"week\", \"month\", \"year\"]),\n interval: z.number().int().min(1),\n dayOfMonth: z.union([z.number().int().min(1).max(31), z.literal(\"last\")]).optional(),\n })\n .strict();\n\n/** Field-driven recurrence: pick the interval per-record by an `enum`\n * field's value — one collection can mix daily / weekly / monthly\n * obligations in a single list. `map` keys are validated to exactly cover\n * that field's `values` by a `CollectionSchemaZ` refine (which can see the\n * sibling `fields`); here each map value just has to be a well-formed\n * literal `every`. `.strict()` mirrors the literal arm so a both-keys\n * object fails this arm too. */\nexport const EveryFieldDrivenZ = z\n .object({\n fromField: z.string().trim().min(1),\n map: z.record(z.string(), EveryLiteralZ),\n })\n .strict();\n\n/** Either a single literal interval (applied to every record) or the\n * field-driven map. Two `.strict()` arms mean \"both keys\" and \"neither\n * key\" both fail validation, with no extra refine. */\nexport const EveryZ = z.union([EveryLiteralZ, EveryFieldDrivenZ]);\n\n/** Host-driven recurrence: when a record satisfies `when` (default:\n * \"`completionField` value ∈ `completionDoneValues`\"), the host creates the\n * next record with a forward-advanced `triggerField` date. `carry` copies\n * record fields verbatim onto the successor; `set` forces fixed values\n * (typically resetting the status field to its pending value). The\n * successor's id and contents are a pure function of (source record, this\n * rule); creation is create-if-absent, so the mechanism stays convergent. */\nexport const SpawnZ = z.object({\n when: WhenZ.optional(),\n every: EveryZ,\n carry: z.array(z.string().trim().min(1)).optional(),\n set: z.record(z.string(), z.unknown()).optional(),\n});\n\n// ---------------------------------------------------------------------------\n// Ingest (scheduled retrieval)\n// ---------------------------------------------------------------------------\n\n/** Declarative retrieval config for a Feed (a collection that refills itself\n * from the internet). `http-json` needs `itemsAt` (a path to the items\n * array) only when the response body isn't itself the array; rss/atom yield\n * items natively and ignore it — so no kind-specific requirement here. */\nexport const DeclarativeIngestZ = z.object({\n kind: z.enum(INGEST_KINDS),\n url: z.url(),\n schedule: z.enum(FEED_SCHEDULES),\n // Optional UTC hour (0–23) to anchor a `daily` schedule; ignored otherwise.\n atHour: z.number().int().min(0).max(23).optional(),\n itemsAt: z.string().trim().min(1).optional(),\n map: z.record(z.string().trim().min(1), z.string().trim().min(1)),\n idFrom: z.string().trim().min(1).optional(),\n maxItems: z.number().int().min(0).optional(),\n});\n\n/** Agent-performed retrieval. Valid on any collection (the primary consumer\n * is skill-backed collections — feeds keep their declarative kinds). No\n * `url`/`map`: the worker owns retrieval and record shape, seeded by\n * `template` + a summary of every record, run in `role`. `template` is\n * validated the SAME way an action's template is (safe path under\n * `templates/`), so the skill-bridge mirrors it identically. */\nexport const AgentIngestZ = z.object({\n kind: z.literal(AGENT_INGEST_KIND),\n schedule: z.enum(FEED_SCHEDULES),\n // Optional UTC hour (0–23) to anchor a `daily` schedule; ignored otherwise.\n atHour: z.number().int().min(0).max(23).optional(),\n role: z.string().trim().min(1),\n template: z\n .string()\n .trim()\n .min(1)\n .refine(isSafeActionTemplatePath, \"must be a safe path under `templates/` (e.g. `templates/refresh.md`; no `..`, no leading `/`, no backslash)\"),\n});\n\n/** The Google event fields a collection may pull from. `id` is absent on\n * purpose — it always lands in the primary field, since upsert-by-event-id\n * is what makes the sync idempotent. */\nexport const GOOGLE_CALENDAR_SOURCE_FIELDS = [\"summary\", \"start\", \"end\", \"htmlLink\", \"colorId\", \"status\", \"description\", \"location\"] as const;\n\n/** Marks a collection as the destination of the LLM-free Google Calendar\n * sync (#2095). `map` is collectionField → Google event field, so the user's\n * collection keeps whatever field names it already uses. */\nexport const GoogleCalendarSyncZ = z.object({\n /** Calendar to pull from; defaults to the user's primary. */\n calendarId: z.string().trim().min(1).optional(),\n // An empty map is silently useless rather than harmless: the sync would run\n // and write a record per event carrying ONLY the event id, so the user gets\n // rows with no content. Fail at load instead.\n map: z.record(z.string().trim().min(1), z.enum(GOOGLE_CALENDAR_SOURCE_FIELDS)).refine((map) => Object.keys(map).length > 0, {\n message: \"map at least one field — a `googleCalendar` sync with an empty map writes records that carry only the event id\",\n }),\n /** Run the push on the sync schedule too, immediately before each pull, so\n * local edits reach Google without anyone pressing a button (#2620).\n *\n * Opt-in and absent by default: a push writes to a calendar other people may\n * read, so turning it on is the user's decision, not a default. */\n autoPush: z.boolean().optional(),\n});\n\n/** `ingest` is a discriminated union on `kind`: the three declarative\n * retrievers fetch-and-map; `agent` dispatches a hidden worker. Optional on\n * every schema — skill-backed collections usually omit it; only feeds\n * discovered from `<workspace>/feeds/` are REQUIRED to carry it (gated by\n * `acceptParsedSchema`). */\nexport const IngestZ = z.discriminatedUnion(\"kind\", [DeclarativeIngestZ, AgentIngestZ]);\n\n// ---------------------------------------------------------------------------\n// dynamicIcon (data-driven launcher icon) and its `where` predicate\n// ---------------------------------------------------------------------------\n\n// Data-driven launcher-icon override (see `CollectionSchema.dynamicIcon`).\n// `source.collection` may name ANY collection (self or cross-collection),\n// so — unlike `ref`/`embed`/`when.field` elsewhere in this file — its\n// shape is validated here without a cross-field refine against a specific\n// target schema (that collection may not even be loaded yet); a bad\n// `source.collection`/`orderBy`/condition `field` fails soft at compute\n// time instead (`computeCollectionIcon`), matching this feature's locked\n// design (see plans/done/feat-dynamic-collection-icons.md \"Open questions\").\n//\n// `where` is the AND-of-conditions predicate defined at the top of this\n// file (`WhereZ`, shared with `flag` fields) — see `./where` for the\n// evaluator.\nexport const DynamicIconSourceZ = z.object({\n collection: z.string().trim().min(1),\n from: z.enum([\"latest\", \"first\", \"when\"]).optional(),\n orderBy: z.string().trim().min(1).optional(),\n where: WhereZ.optional(),\n});\nexport const DynamicIconRuleZ = z.object({\n where: WhereZ,\n icon: z.string().trim().min(1),\n});\nexport const DynamicIconSpecZ = z.object({\n source: DynamicIconSourceZ,\n rules: z.array(DynamicIconRuleZ),\n fallback: z.string().trim().min(1).optional(),\n});\n\n// ---------------------------------------------------------------------------\n// dataSource (external read-only data file)\n// ---------------------------------------------------------------------------\n\n/** External-data collection: the records ARE the rows of a user-supplied\n * data file (v1: CSV), queried through DuckDB — never copied into\n * `<dataDir>/<id>.json` files. Declaring `dataSource` makes the collection\n * **read-only** in every UI/tool write path; updates happen by replacing /\n * editing the file itself (file-watch republishes the views). `path` is\n * workspace-relative and containment-checked exactly like `dataPath`. The\n * row-id column is the schema's existing `primaryKey` — there is\n * deliberately no second key concept here.\n * See plans/done/feat-collection-csv-duckdb-source.md. */\nexport const DataSourceZ = z.object({\n type: z.literal(\"csv\"),\n path: z.string().min(1),\n});\n\n// ---------------------------------------------------------------------------\n// storage (alternative writable record backend)\n// ---------------------------------------------------------------------------\n\n/** Alternative WRITABLE storage backend for a collection's records —\n * unlike `dataSource` (external read-only file), a `storage` collection\n * behaves like a normal writable collection; only where the rows live\n * changes. The store factory registry (`server/store.ts`) picks the\n * implementation by `type` (plans/done/refactor-storage-virtualization.md).\n *\n * A discriminated union rather than one shape with optional keys, because\n * only the sqlite variant is a workspace FILE: its `path` is\n * workspace-relative and containment-checked exactly like `dataPath`, while\n * the firestore variant has no path to check — its records are not on this\n * machine at all. Optional keys would let each arm accept the other's, and\n * the compiler would stop being the thing that tells you which. */\nexport const StorageZ = z.discriminatedUnion(\"type\", [\n /** Records in a single SQLite database file (`node:sqlite`, one JSON\n * record per row keyed by the primaryKey). */\n z.object({\n type: z.literal(\"sqlite\"),\n path: z.string().min(1),\n }),\n /** Records as Firestore documents of a SHARED collection, at\n * `apps/{aid}/collections/{cid}/items/{id}`.\n *\n * This variant declares NO location, and that is the whole shape of it:\n * `aid` comes from the repository's `app.json` (it is one per app, not one\n * per collection — four collections share one member roster), and `cid` is\n * always this collection's slug. There is nothing left for a schema to say.\n *\n * `.strict()` (the same reason the `spawn` arms use it): stripping an\n * unknown key silently would confirm a wrong mental model — an author who\n * writes `path` believes their records land there, and an author who writes\n * `cid` believes the collection can be named something other than its slug.\n * Only this new arm is strict; the sqlite arm stays permissive so an\n * existing schema carrying a stray key doesn't start failing. */\n z\n .object({\n type: z.literal(\"firestore\"),\n })\n .strict(),\n]);\n\n// ---------------------------------------------------------------------------\n// The whole schema\n// ---------------------------------------------------------------------------\n\n/** The schema's SHAPE, before any cross-field rule runs. Named so the rules in\n * `./schemaRules` can type their argument against it — they receive whatever\n * this parses to, and nothing narrower. */\nconst CollectionObjectZ = z.object({\n title: z.string().min(1),\n icon: z.string().min(1),\n // Exactly one of `dataPath` (native JSON-file records), `dataSource`\n // (external read-only data file), or `storage` (alternative writable\n // backend) — enforced by a refine below.\n dataPath: z.string().min(1).optional(),\n dataSource: DataSourceZ.optional(),\n storage: StorageZ.optional(),\n primaryKey: z.string().min(1),\n // When set, the collection holds at most one record whose primary\n // key is this exact value (e.g. `me` for the business profile).\n // The host fixes the create form's primary key to it and hides the\n // Add button once the record exists.\n singleton: z.string().trim().min(1).optional(),\n fields: z.record(z.string(), FieldSpecZ),\n actions: z.array(ActionSpecZ).optional(),\n // Collection-level actions (header buttons). Same shape as `actions`;\n // the `when` predicate is ignored (no record context). The seed\n // prompt injects a progress summary of all records instead.\n collectionActions: z.array(ActionSpecZ).optional(),\n // Completion-tracking pair: when both are set, item-create fires a\n // notification that clears once `completionField` transitions into\n // `completionDoneValues`. The two are bound together — declaring\n // one without the other is a misconfiguration the cross-field\n // refine below rejects.\n completionField: z.string().trim().min(1).optional(),\n completionDoneValues: z.array(z.string().trim().min(1)).min(1).optional(),\n // Optional human-readable label for the completion notification's\n // title — names the field whose value reads better than the opaque\n // primaryKey (e.g. a `name` field). Falls back to the primaryKey\n // value at render time when unset or empty.\n displayField: z.string().trim().min(1).optional(),\n // Time gate: names a `date` field that delays the completion bell\n // until the clock reaches it. Requires the completion pair (the bell\n // still clears via the done value). Validated to name a real `date`\n // field by refines below.\n triggerField: z.string().trim().min(1).optional(),\n // Lead time in whole days — fire the bell this many days before\n // `triggerField`. Non-negative; requires `triggerField` (refine below).\n triggerLeadDays: z.number().int().min(0).optional(),\n // Host-driven recurrence; requires `triggerField`. See SpawnZ.\n spawn: SpawnZ.optional(),\n // Calendar view anchor: names a `date` field whose value places each\n // record on a month grid. Validated to name a real `date` field by a\n // refine below. Optional — the toggle auto-derives from any `date`\n // field when this is unset.\n calendarField: z.string().trim().min(1).optional(),\n // Multi-day span end: a second `date` field the calendar record spans\n // to. Requires `calendarField`; validated to name a real `date` field.\n calendarEndField: z.string().trim().min(1).optional(),\n // Day (time-allocation) view time source: names a string field holding a\n // free-form time or time-range (e.g. \"14:00-17:00\", \"17:00-\", \"16:30\").\n // Consulted only when the date fields are date-only. Requires\n // `calendarField`; validated to name a real field by a refine below.\n calendarTimeField: z.string().trim().min(1).optional(),\n // Kanban board group: names an `enum` field whose value buckets each\n // record into a column. Validated to name a real `enum` field by a\n // refine below. Optional — the toggle auto-derives from any `enum`\n // field when this is unset.\n kanbanField: z.string().trim().min(1).optional(),\n // Custom (LLM-authored) HTML views. Each renders in a sandboxed iframe\n // over the records. Optional, so every existing schema validates\n // unchanged. Ids validated to be valid + unique slugs by refines below.\n views: z.array(CustomViewZ).optional(),\n // Completion-bell gate: only notify for records matching this predicate\n // (e.g. high-priority todos). Reuses the `when` shape; requires\n // `completionField`; field validated to exist by refines below.\n notifyWhen: WhenZ.optional(),\n // Declarative retrieval config. Present only on Feeds (collections in\n // the `<workspace>/feeds/` registry). Optional, so every existing\n // skill schema validates unchanged.\n ingest: IngestZ.optional(),\n // Declares this collection as the destination of the LLM-free Google\n // Calendar sync. Optional, so every existing schema validates unchanged.\n googleCalendar: GoogleCalendarSyncZ.optional(),\n // Data-driven launcher-icon override. Optional, so every existing\n // schema validates unchanged; `source` is required within it.\n dynamicIcon: DynamicIconSpecZ.optional(),\n});\n\nexport type CollectionSchemaInput = z.infer<typeof CollectionObjectZ>;\n\nconst BareCollectionSchemaZ = CollectionObjectZ\n // Exactly one storage declaration: native records need `dataPath`, an\n // external data file needs `dataSource`, an alternative backend needs\n // `storage`. Zero (nowhere to read) and several (ambiguous which wins)\n // are equally meaningless — fail loudly at load instead of picking\n // silently.\n .refine(declaresExactlyOneStore, {\n message:\n \"declare exactly one of `dataPath` (native JSON records), `dataSource` (external read-only data file), or `storage` (alternative writable backend)\",\n path: [\"dataPath\"],\n })\n // NOTE: `storage` collections support the full write machinery\n // (`spawn` / `completionField` / `triggerField` / `singleton` / `ingest`\n // / mutate actions) — spawn and the watcher reconcilers go through the\n // CollectionStore seam. What DRIVES that reconciliation differs by\n // backend: `sqlite` has a db-file watcher, while `firestore` has no file\n // to watch and is reconciled on the clock tick instead\n // (`tickUnwatchedCollections`) until the snapshot listener lands. Either\n // way the declared behaviour runs — that equivalence is what lets this\n // refine stay backend-agnostic.\n // A `dataSource` collection is read-only by definition, so schema-level\n // write machinery can never fire: `singleton` pins CREATES, `ingest`\n // REFILLS records, `spawn` WRITES successor records. Rejecting them at\n // validation kills whole classes of writes before any runtime guard.\n .refine(dataSourceDeclaresNoWriteMachinery, {\n message: \"a `dataSource` collection is read-only — it cannot declare `singleton`, `ingest`, `spawn`, or `googleCalendar` (all of them write records)\",\n path: [\"dataSource\"],\n })\n // The sync writes each mapped value into a declared field, and puts the\n // Google event id in the primary field — so a map key that names no field\n // (or names the primary) would silently drop data or fight the id.\n .refine(googleCalendarMapNamesStoredFields, {\n message: \"a `googleCalendar` map key must name a declared, non-computed field, and never the primaryKey (that always holds the Google event id)\",\n path: [\"googleCalendar\"],\n })\n // Same rule for declarative host writes: a mutate action writes the\n // record it's invoked on.\n .refine(dataSourceDeclaresNoMutateAction, {\n message: 'a `dataSource` collection is read-only — its actions cannot use `kind: \"mutate\"` (a host write); use `chat`/`agent` actions instead',\n path: [\"dataSource\"],\n })\n // The singleton value becomes a record id (and thus a `<id>.json`\n // filename), so it must satisfy the SAME record-id rule the write path\n // enforces — otherwise the create form would lock the primary key to a\n // value the POST route then rejects as an invalid item id, making the\n // collection impossible to initialize (Codex P1).\n .refine(singletonIsAValidRecordId, {\n message: \"schema `singleton` must be a valid item id (alphanumeric / hyphen / underscore / interior dot, no `..` or path separators)\",\n path: [\"singleton\"],\n })\n // Action ids must be unique so the dispatch route resolves\n // unambiguously.\n .refine(actionIdsAreUnique, {\n message: \"schema `actions` must have unique `id`s\",\n path: [\"actions\"],\n })\n // Collection-level action ids must likewise be unique.\n .refine(collectionActionIdsAreUnique, {\n message: \"schema `collectionActions` must have unique `id`s\",\n path: [\"collectionActions\"],\n })\n // A mutate action's `set` writes real STORED fields: a typo'd key\n // would write a stray value forever, a computed/projected field is\n // never persisted, and the primaryKey is the filename (renaming is\n // not a mutation).\n .refine(mutateSetKeysNameStoredFields, {\n message: \"a mutate action's `set` keys must name declared, non-computed fields (and never the primaryKey)\",\n path: [\"actions\"],\n })\n // Every `$params.<name>` reference in `set` must name a declared\n // param — an undeclared one would silently no-op the assignment.\n .refine(mutateParamRefsAreDeclared, {\n message: \"a mutate action's `$params.<name>` references must name keys declared in its `params`\",\n path: [\"actions\"],\n })\n // A collection-level action has no record to write.\n .refine(collectionActionsAreNotMutate, {\n message: '`collectionActions` cannot contain `kind: \"mutate\"` — a collection-level action has no record to write',\n path: [\"collectionActions\"],\n })\n // A `currencyField` pointer must name a real top-level field that\n // holds a code string — a typo (`curreny`) would otherwise pass the\n // per-field check, then silently fall back to the literal / USD at\n // render and mislabel amounts. Checked at the schema level because a\n // field can't see its siblings.\n .refine(currencyFieldRefsNameCodeFields, {\n message: \"a money field's `currencyField` must name a top-level `string`, `text`, or `enum` field that holds the currency code\",\n path: [\"fields\"],\n })\n // Completion-tracking pair must be declared together: declaring\n // `completionField` without `completionDoneValues` (or vice-versa)\n // is meaningless — the host would either never fire (no done values\n // to compare against) or never clear (no field to read). Bound\n // together so the misconfiguration fails loudly at load time.\n // EXCEPTION: when `completionField` names a `flag` field, done ⇔ the\n // flag's `where` matches, so `completionDoneValues` carries no\n // information and MUST be omitted (declaring it would invite a\n // contradictory second source of truth).\n .refine(completionPairIsCoherent, {\n message:\n \"schema `completionField` and `completionDoneValues` must be declared together (both set, or both omitted) — unless `completionField` names a `flag` field, in which case `completionDoneValues` must be omitted (done ⇔ the flag matches)\",\n path: [\"completionField\"],\n })\n // `completionField` must name a real top-level field — a typo would\n // silently disable the notification mechanism otherwise.\n .refine(completionFieldIsDeclared, {\n message: \"schema `completionField` must name a top-level field declared in `fields`\",\n path: [\"completionField\"],\n })\n // `displayField`, like `completionField`, must name a real top-level\n // field — a typo would silently fall back to the primaryKey forever.\n .refine(displayFieldIsDeclared, {\n message: \"schema `displayField` must name a top-level field declared in `fields`\",\n path: [\"displayField\"],\n })\n // A field's `when.field` gates its visibility against a sibling's\n // value, so it must name a real top-level field — a typo would\n // silently keep the field hidden forever (the gate never matches).\n // Checked at the schema level because a field can't see its siblings.\n .refine(fieldVisibilityGatesNameDeclaredFields, {\n message: \"a field's `when.field` must name a top-level field declared in `fields`\",\n path: [\"fields\"],\n })\n // A flag's `where` reads sibling fields (both `cond.field` and a\n // same-record `valueFrom.field`), so each must name a real top-level\n // field — a typo would silently pin the flag false forever (`ne`:\n // true forever). Checked at the schema level because a field can't\n // see its siblings.\n .refine(flagConditionsNameDeclaredFields, {\n message: \"a flag field's `where` conditions must name top-level fields declared in `fields` (both `field` and a same-record `valueFrom.field`)\",\n path: [\"fields\"],\n })\n // A flag named by `completionField` is evaluated against the RAW\n // record — the reconciler (and spawn's fallback) read items straight\n // off disk, BEFORE any `deriveAll` enrichment — so its `where` may\n // only reference STORED fields. A condition over a computed sibling\n // (derived/rollup/toggle/flag/embed/backlinks) would see an absent\n // key: `ne` matches vacuously, every other op reads false, and the\n // bell would clear wrongly / never. General (non-completion) flags\n // keep the full vocabulary — the UI evaluates them post-enrichment.\n .refine(completionFlagReadsOnlyStoredFields, {\n message:\n \"a `flag` named by `completionField` may only reference STORED fields in its `where` — completion is evaluated against the raw record (before deriveAll), where computed values (derived/rollup/toggle/flag/embed/backlinks) are absent\",\n path: [\"completionField\"],\n })\n // The spawn-inert guard (`spawnSuccessorStartsInert`) statically\n // checks that a successor is not born already matching the spawn\n // predicate; it cannot see through a flag's `where` (the predicate\n // would need full record evaluation against `set`/`carry`). So a\n // schema whose completion is flag-form may only spawn with an\n // explicit `spawn.when` — which the guard CAN check.\n .refine(flagCompletionSpawnDeclaresWhen, {\n message:\n \"a schema whose `completionField` names a `flag` field must declare an explicit `spawn.when` (the spawn-inert check cannot statically evaluate a flag's `where`)\",\n path: [\"spawn\"],\n })\n // An `embed`'s `idField` resolves the target record id from a sibling's\n // value, so it must name a real top-level field — and one whose stored\n // value is a plain id string. Only `ref` / `string` qualify: the editor\n // writes the picked id into that field, so a non-persisted or composite\n // type (`embed` / `derived` / `toggle` / `table` / `number` / …) would\n // either not round-trip on save or hold no usable id. Restricting it\n // makes the misconfiguration fail at schema load, not silently at\n // render. `idField` is ignored on non-`embed` fields, so only check\n // there. Schema-level because a field can't see its siblings.\n .refine(embedIdFieldsNameIdBearingFields, {\n message: \"an embed field's `idField` must name a top-level `ref` or `string` field declared in `fields`\",\n path: [\"fields\"],\n })\n // `triggerField` requires the completion pair: the time gate only\n // suppresses the *completion* bell until the date, and the bell still\n // clears via `completionDoneValues`. Without completion there is no\n // bell to gate (or clear), so the declaration is meaningless.\n .refine(triggerFieldRequiresCompletion, {\n message: \"schema `triggerField` requires `completionField` / `completionDoneValues` (the gated bell still clears via the done value)\",\n path: [\"triggerField\"],\n })\n // `triggerField` must name a real `date` field — the gate parses its\n // value as `YYYY-MM-DD`; any other type can't be compared to the clock.\n .refine(triggerFieldIsADateField, {\n message: \"schema `triggerField` must name a top-level `date` field declared in `fields`\",\n path: [\"triggerField\"],\n })\n // `triggerLeadDays` only means something relative to a trigger date.\n .refine(triggerLeadDaysRequiresTriggerField, {\n message: \"schema `triggerLeadDays` requires `triggerField` (it shifts when that field's bell fires)\",\n path: [\"triggerLeadDays\"],\n })\n // `spawn` advances `triggerField` to compute the successor's trigger\n // date, so the schema must declare one.\n .refine(spawnRequiresTriggerField, {\n message: \"schema `spawn` requires `triggerField` (the successor's trigger date is `triggerField` advanced by `spawn.every`)\",\n path: [\"spawn\"],\n })\n // `spawn.when.field` and every `spawn.carry` entry must name real\n // top-level fields — a typo would silently never match / never copy.\n .refine(spawnWhenFieldIsDeclared, {\n message: \"schema `spawn.when.field` must name a top-level field declared in `fields`\",\n path: [\"spawn\"],\n })\n .refine(spawnCarryEntriesAreDeclared, {\n message: \"every `spawn.carry` entry must name a top-level field declared in `fields`\",\n path: [\"spawn\"],\n })\n // A successor must NOT be born already matching its own spawn predicate\n // — it would re-spawn on its first reconcile, fanning out into an\n // unbounded chain of records. The predicate field/values are `spawn.when`\n // when given, else the completion-done pair (the default predicate). The\n // successor's value for that field is `set[field]` if set, else the\n // carried source value (which matched, by definition, when the spawn\n // fired) if carried, else absent (safe). Reject the first two when they\n // land on a matching value.\n .refine(spawnSuccessorStartsInert, {\n message:\n \"`spawn` must leave the successor in a non-matching state (e.g. `set` the status to a pending value); seeding the predicate field to a matching value via `set`/`carry` would respawn forever\",\n path: [\"spawn\"],\n })\n // Field-driven `spawn.every` (§4.1): `fromField` must name a top-level\n // `enum` — the only field type with a closed, finite value set to drive\n // the `map` and the form `<select>`.\n .refine(fieldDrivenFromFieldIsEnum, {\n message: \"`spawn.every.fromField` must name a top-level `enum` field declared in `fields`\",\n path: [\"spawn\"],\n })\n // Field-driven `spawn.every` (§4.2): the `map` keys must exactly cover the\n // enum's `values` — a missing key would stall a record at that frequency;\n // an extra key signals a map left stale after an enum edit.\n .refine(fieldDrivenMapCoversValues, {\n message: \"`spawn.every.map` keys must exactly cover the `values` of the `enum` named by `fromField` (no missing or extra keys)\",\n path: [\"spawn\"],\n })\n // Field-driven `spawn.every` (§4.5): `fromField` must be carried (or `set`)\n // onto the successor, or the next spawn in the chain can't resolve an\n // interval and the recurrence silently halts.\n .refine(fieldDrivenFromFieldCarried, {\n message:\n \"`spawn.every.fromField` must appear in `spawn.carry`, or be written by `spawn.set` to a value present in `spawn.every.map`, so the successor keeps a resolvable recurrence interval\",\n path: [\"spawn\"],\n })\n // `calendarField` must name a real `date`/`datetime` field — the calendar\n // view parses its value to place records on the month grid (a `datetime`\n // anchor also carries the clock for the day view); any other type can't be\n // put on a calendar.\n .refine(calendarFieldIsDateLike, {\n message: \"schema `calendarField` must name a top-level `date` or `datetime` field declared in `fields`\",\n path: [\"calendarField\"],\n })\n // `calendarEndField` marks the end of a multi-day span, so it only means\n // something alongside a start anchor.\n .refine(calendarEndFieldRequiresCalendarField, {\n message: \"schema `calendarEndField` requires `calendarField` (it marks the end of the span that starts at `calendarField`)\",\n path: [\"calendarEndField\"],\n })\n // `calendarEndField` must also name a real `date`/`datetime` field — same parse.\n .refine(calendarEndFieldIsDateLike, {\n message: \"schema `calendarEndField` must name a top-level `date` or `datetime` field declared in `fields`\",\n path: [\"calendarEndField\"],\n })\n // `calendarTimeField` places records on the day view, so it only means\n // something alongside a start anchor.\n .refine(calendarTimeFieldRequiresCalendarField, {\n message: \"schema `calendarTimeField` requires `calendarField` (it supplies the time-of-day for the calendar's day view)\",\n path: [\"calendarTimeField\"],\n })\n // `calendarTimeField` must name a real top-level field (a free-form time\n // string the day view parses).\n .refine(calendarTimeFieldIsDeclared, {\n message: \"schema `calendarTimeField` must name a top-level field declared in `fields`\",\n path: [\"calendarTimeField\"],\n })\n // …and that field must be string-backed — the day view parses its value as a\n // time string, so a number/enum/date column can't drive it.\n .refine(calendarTimeFieldIsStringBacked, {\n message: \"schema `calendarTimeField` must name a top-level `string` or `text` field declared in `fields`\",\n path: [\"calendarTimeField\"],\n })\n // `kanbanField` must name a real `enum` field — the board groups records\n // into one column per declared enum value; any other type has no closed\n // set of columns to group by.\n .refine(kanbanFieldIsAnEnum, {\n message: \"schema `kanbanField` must name a top-level `enum` field declared in `fields`\",\n path: [\"kanbanField\"],\n })\n // A `toggle` field projects an `enum` field: its `field` must name a real\n // top-level enum, and `onValue` / `offValue` must be members of that\n // enum's `values` — otherwise toggling would write a value outside the\n // closed set (and never appear \"checked\").\n .refine(togglesProjectValidEnums, {\n message: \"a `toggle` field's `field` must name a top-level `enum` field, and its `onValue`/`offValue` must be values of that enum\",\n path: [\"fields\"],\n })\n // `notifyWhen` narrows the completion bell, so it only means something with\n // completion tracking, and its `field` must name a real top-level field.\n .refine(notifyWhenRequiresCompletion, {\n message: \"schema `notifyWhen` requires `completionField` (it narrows that bell)\",\n path: [\"notifyWhen\"],\n })\n .refine(notifyWhenFieldIsDeclared, {\n message: \"schema `notifyWhen.field` must name a top-level field declared in `fields`\",\n path: [\"notifyWhen\"],\n })\n // Every custom view `id` must be a valid slug — it doubles as the\n // view-mode selector key (`custom:<id>`) and the capability-token clamp\n // key, both of which expect a path-safe token.\n .refine(viewIdsAreSlugs, {\n message: \"every `views[].id` must be a valid slug (alphanumeric / hyphen / underscore, no path separators)\",\n path: [\"views\"],\n })\n // Custom view ids must be unique so the selector + token clamp resolve\n // unambiguously.\n .refine(viewIdsAreUnique, {\n message: \"schema `views` must have unique `id`s\",\n path: [\"views\"],\n });\n\n// ---------------------------------------------------------------------------\n// Prototype-sensitive field names — checked on the RAW input\n// ---------------------------------------------------------------------------\n\n// A field name becomes a plain-object key THROUGHOUT the engine — record\n// JSON, edit drafts, enrichment output, view filter state — where a\n// prototype-sensitive name would read (or write) inherited prototype data\n// instead of field data (Codex review on PR #2176: a flag named\n// `__proto__` jams its filter chip). This check must run BEFORE zod\n// parses: zod's record builder silently SKIPS an own `__proto__` input\n// key (pollution safety), so a post-parse refine would never see it and\n// the author's field would just vanish. Hence the `z.preprocess` wrapper\n// below rather than another `.refine`.\nconst PROTOTYPE_KEYS = [\"__proto__\", \"constructor\", \"prototype\"] as const;\n\n/** The first own prototype-sensitive key of `value`, or null. */\nfunction ownPrototypeKey(value: unknown): string | null {\n if (value === null || typeof value !== \"object\") return null;\n for (const key of PROTOTYPE_KEYS) {\n if (Object.hasOwn(value, key)) return key;\n }\n return null;\n}\n\n/** Own enumerable entries of an object (arrays keyed by index), none for\n * anything else — the raw input is unvalidated, so `fields` may be junk. */\nfunction ownEntries(value: unknown): [string, unknown][] {\n if (isUnknownArray(value)) return value.map((entry, index): [string, unknown] => [String(index), entry]);\n return isRecord(value) ? Object.entries(value) : [];\n}\n\n/** The name-defining sub-record a raw field spec (`of`) or action (`params`)\n * carries, or undefined when the holder isn't an object at all. */\nfunction nameDefiningSubRecord(holder: unknown, key: \"of\" | \"params\"): unknown {\n return isRecord(holder) ? holder[key] : undefined;\n}\n\n/** Dotted path of the first prototype-sensitive `params` name across both\n * action lists, or null. */\nfunction prototypeActionParamPath(input: Record<string, unknown>): string | null {\n for (const [listName, list] of [\n [\"actions\", input.actions],\n [\"collectionActions\", input.collectionActions],\n ] as const) {\n for (const action of isUnknownArray(list) ? list : []) {\n const badParam = ownPrototypeKey(nameDefiningSubRecord(action, \"params\"));\n if (badParam !== null) return `${listName}.params.${badParam}`;\n }\n }\n return null;\n}\n\n/** Dotted path of the first prototype-sensitive field name in the raw\n * schema input — top-level `fields`, each table field's `of`, and each\n * action's `params` (the three records that DEFINE names) — or null. */\nfunction prototypeFieldKeyPath(input: unknown): string | null {\n if (!isRecord(input)) return null;\n const bad = ownPrototypeKey(input.fields);\n if (bad !== null) return `fields.${bad}`;\n for (const [key, spec] of ownEntries(input.fields)) {\n const badSub = ownPrototypeKey(nameDefiningSubRecord(spec, \"of\"));\n if (badSub !== null) return `fields.${key}.of.${badSub}`;\n }\n return prototypeActionParamPath(input);\n}\n\nexport const CollectionSchemaZ = z.preprocess((input, ctx) => {\n const bad = prototypeFieldKeyPath(input);\n if (bad !== null) {\n ctx.addIssue({ code: \"custom\", message: `'${bad}': field names must not be prototype-sensitive keys (\\`__proto__\\`, \\`constructor\\`, \\`prototype\\`)` });\n return z.NEVER;\n }\n return input;\n}, BareCollectionSchemaZ);\n","// Discover schema-driven collections. A \"collection\" is a skill\n// directory that ships a `schema.json` alongside its `SKILL.md`.\n// Scans both user (`~/.claude/skills/`) and project\n// (`<workspace>/.claude/skills/`) scopes; project wins on slug\n// collision (mirrors the rule in\n// `server/workspace/skills/discovery.ts`). A host may declare a root to\n// have NO user scope (`paths.userSkillsDir` → null), and then there is no\n// shadowing to reason about: that root sees its own collections and feeds,\n// and nothing else.\n//\n// The schema validator itself lives in `../core/schemaZ` (the zod single\n// source of truth every `../core/schema` type derives from); this module\n// applies it, plus the post-Zod acceptance gates below.\n\nimport { readdir, readFile, stat } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { log, getWorkspaceRoot, userSkillsDir, projectSkillsDir, feedsRoot, hostSupportsSharedCollections } from \"./host\";\nimport { CollectionSchemaZ } from \"../core/schemaZ\";\nimport { SCHEMA_FILE, resolveDataDir, safeSlugName } from \"./paths\";\nimport { appManifestReason, loadAppManifest } from \"./appManifest\";\nimport type { LoadedCollection } from \"./discoveredCollection\";\nimport type { CollectionDetail, CollectionSchema, CollectionSource, CollectionSummary } from \"../core/schema\";\nimport { isErrorWithCode, isRecord } from \"@mulmoclaude/common\";\n\n// Re-exported for the existing `collection/server` importers (manageCollection's\n// putSchema, the registry importWriter) that validate schemas the same way\n// discovery does.\nexport { CollectionSchemaZ };\n\n// The LoadedCollection shape now lives in @mulmoclaude/core/collection/server\n// (imported at the top, re-exported below) so discovery stays its producer and\n// the many `from \"./discovery.js\"` importers resolve it unchanged.\n\n// Normalize an agent-authored feed schema (no register tool to do it):\n// default `icon`, and **force** `dataPath` to the feed-owned namespace\n// `data/feeds/<slug>`. Forcing dataPath (rather than trusting the file) is\n// a safety boundary — a feed can only ever read/write/delete records under\n// its own folder, never another app's data (e.g. `data/wiki`). Non-object\n// input passes through so the Zod error stays clear.\nfunction applyFeedSchemaDefaults(parsed: unknown, slug: string): unknown {\n if (!isRecord(parsed)) return parsed;\n const icon = typeof parsed.icon === \"string\" && parsed.icon.trim().length > 0 ? parsed.icon : \"dynamic_feed\";\n return { ...parsed, icon, dataPath: `data/feeds/${slug}` };\n}\n\n/** Result of the post-Zod acceptance gates: the resolved record dir (and,\n * for a `dataSource` schema, the resolved data file) on success, or a\n * one-line reason discovery would skip the schema. */\nexport type SchemaAcceptance = { ok: true; dataDir: string; dataSourceFile?: string; storageFile?: string; appId?: string } | { ok: false; reason: string };\n\n/** The conventional per-slug records dir a `dataSource` / `storage` collection\n * gets as its `dataDir` (records never live there, but archive/delete paths\n * stay well-defined — same shape the registry's R3 normalization uses).\n *\n * INVARIANT — this is NOT a default `dataPath`, and must not be used as one.\n * It applies only to the two backends whose records are not per-file JSON. A\n * normal collection declares its own location and exactly one of `dataPath` /\n * `dataSource` / `storage`; a schema with none of the three is REJECTED, not\n * quietly pointed here. Handing a per-file collection this path would silently\n * relocate its records away from the folder the user (and its SKILL.md) sees. */\nfunction conventionalDataPath(slug: string): string {\n return `data/collections/${slug}/items`;\n}\n\n/** The declared field named by `primaryKey`, or `undefined` when the schema\n * declares no such field. Own-property guarded: a `primaryKey` of `toString`\n * / `constructor` / `__proto__` must miss here, not read an Object.prototype\n * member and slip past the \"is it a declared field?\" gate into the wrong\n * \"add `primary: true`\" advice. Shared with manageCollection's putSchema\n * gate so both report the SAME reason. */\nexport function resolvePrimaryField(fields: CollectionSchema[\"fields\"], primaryKey: string): CollectionSchema[\"fields\"][string] | undefined {\n return Object.hasOwn(fields, primaryKey) ? fields[primaryKey] : undefined;\n}\n\n/** The acceptance gates discovery applies AFTER `CollectionSchemaZ` parses,\n * before a schema becomes a live collection:\n *\n * - the `primaryKey` must be a declared field flagged `primary: true` —\n * without the flag CollectionView renders the field editable, and a\n * rename is silently pinned back to the URL itemId on save, so the user's\n * edit is dropped with no error;\n * - a `feed` schema must declare an `ingest` block (else it's a dead,\n * non-refreshable card);\n * - `dataPath` — or a `dataSource`'s `path` — must resolve INSIDE the\n * workspace (same realpath containment for both).\n *\n * Exported so `manageCollection`'s `putSchema` can run the SAME gates before\n * it reports success — a schema that passes `CollectionSchemaZ` but fails one\n * of these would otherwise write cleanly yet be skipped on the next discovery,\n * hiding the collection (the exact failure that tool exists to prevent). */\nexport function acceptParsedSchema(schema: CollectionSchema, opts: { source: CollectionSource; workspaceRoot: string; slug: string }): SchemaAcceptance {\n const primaryField = resolvePrimaryField(schema.fields, schema.primaryKey);\n if (!primaryField) return { ok: false, reason: `primaryKey '${schema.primaryKey}' is not one of the declared fields` };\n if (primaryField.primary !== true) return { ok: false, reason: `the primaryKey field '${schema.primaryKey}' must be flagged \\`primary: true\\`` };\n if (opts.source === \"feed\" && !schema.ingest) return { ok: false, reason: \"a feed schema must declare an `ingest` block\" };\n if (schema.dataSource !== undefined) {\n // Same containment math as dataPath — resolveDataDir doesn't require\n // the target to exist, so it validates a file path just as well.\n const dataSourceFile = resolveDataDir(schema.dataSource.path, opts.workspaceRoot);\n if (dataSourceFile === null) return { ok: false, reason: `dataSource.path '${schema.dataSource.path}' escapes the workspace` };\n const dataDir = resolveDataDir(conventionalDataPath(opts.slug), opts.workspaceRoot);\n if (dataDir === null) return { ok: false, reason: `slug '${opts.slug}' yields no workspace-contained data dir` };\n return { ok: true, dataDir, dataSourceFile };\n }\n if (schema.storage !== undefined) return acceptStorageSchema(schema.storage, opts);\n const dataDir = resolveDataDir(schema.dataPath ?? \"\", opts.workspaceRoot);\n if (dataDir === null) return { ok: false, reason: `dataPath '${schema.dataPath}' escapes the workspace` };\n return { ok: true, dataDir };\n}\n\n/** The `storage` arm of the acceptance gate. Every storage backend gets the\n * conventional phantom dataDir; what differs is what else has to resolve\n * before the collection can exist at all.\n *\n * A FILE-backed backend (sqlite) resolves and containment-checks a\n * `storageFile`. A SHARED one (firestore) has no path on this machine — it\n * resolves an IDENTITY instead: the `aid` from the repository's `app.json`,\n * which together with the slug as `cid` names `apps/{aid}/collections/{cid}`.\n *\n * Resolving it HERE, once, is the point. The store then receives a settled\n * `(aid, cid)` and never reads `app.json` itself — otherwise the questions of\n * caching, staleness and what to do when the file is missing would be decided\n * inside a read path, where the only cheap answer is to return nothing, and\n * \"this collection is misconfigured\" would reach the user as \"this collection\n * is empty\". A missing or malformed `app.json` is a CONFIGURATION error, so it\n * is reported the same way an escaping `storage.path` is: the schema is\n * refused, with a reason naming the file to create. */\nfunction acceptStorageSchema(storage: NonNullable<CollectionSchema[\"storage\"]>, opts: { workspaceRoot: string; slug: string }): SchemaAcceptance {\n const dataDir = resolveDataDir(conventionalDataPath(opts.slug), opts.workspaceRoot);\n if (dataDir === null) return { ok: false, reason: `slug '${opts.slug}' yields no workspace-contained data dir` };\n if (storage.type === \"sqlite\") {\n const storageFile = resolveDataDir(storage.path, opts.workspaceRoot);\n if (storageFile === null) return { ok: false, reason: `storage.path '${storage.path}' escapes the workspace` };\n return { ok: true, dataDir, storageFile };\n }\n // A shared collection needs a host that can reach Firestore AND a host whose\n // roots are project repositories (D5). Both are the same question from the\n // engine's side, and the host answers it: see `CollectionHost.sharedCollections`.\n // Refused rather than skipped, so the author is told why instead of watching\n // the collection vanish from discovery.\n if (!hostSupportsSharedCollections()) {\n return { ok: false, reason: \"this host does not support shared collections — they live in a project repository, not a managed workspace\" };\n }\n const manifest = loadAppManifest(opts.workspaceRoot);\n if (!manifest.ok) return { ok: false, reason: appManifestReason(manifest, opts.workspaceRoot) };\n return { ok: true, dataDir, appId: manifest.manifest.aid };\n}\n\nasync function loadOneCollection(skillsRoot: string, slug: string, source: CollectionSource, workspaceRoot: string): Promise<LoadedCollection | null> {\n const safeName = safeSlugName(slug);\n if (safeName === null) return null;\n const schemaPath = path.join(skillsRoot, safeName, SCHEMA_FILE);\n let raw: string;\n try {\n const fileStat = await stat(schemaPath);\n if (!fileStat.isFile()) return null;\n raw = await readFile(schemaPath, \"utf-8\");\n } catch (err) {\n if (!isErrorWithCode(err) || err.code !== \"ENOENT\") {\n log.warn(\"collections\", \"failed to read schema.json, skipping\", { slug: safeName, path: schemaPath, error: String(err) });\n }\n return null;\n }\n\n let parsedJson: unknown;\n try {\n parsedJson = JSON.parse(raw);\n } catch (err) {\n log.warn(\"collections\", \"schema.json is not valid JSON, skipping\", { slug: safeName, error: String(err) });\n return null;\n }\n\n // Feeds are authored by the agent as plain files (no register tool), so\n // fill the boilerplate icon / dataPath if omitted before validation.\n const candidate = source === \"feed\" ? applyFeedSchemaDefaults(parsedJson, safeName) : parsedJson;\n const parsed = CollectionSchemaZ.safeParse(candidate);\n if (!parsed.success) {\n log.warn(\"collections\", \"schema.json failed validation, skipping\", { slug: safeName, issues: parsed.error.issues });\n return null;\n }\n\n // Post-Zod acceptance gates (primaryKey flagged primary, feed ingest,\n // workspace-contained dataPath) — shared with manageCollection's putSchema\n // so a validated write and discovery agree on what's a live collection.\n const schema = parsed.data;\n const acceptance = acceptParsedSchema(schema, { source, workspaceRoot, slug: safeName });\n if (!acceptance.ok) {\n log.warn(\"collections\", \"schema.json rejected after validation, skipping\", { slug: safeName, reason: acceptance.reason });\n return null;\n }\n\n return {\n slug: safeName,\n source,\n schema,\n dataDir: acceptance.dataDir,\n ...(acceptance.dataSourceFile !== undefined ? { dataSourceFile: acceptance.dataSourceFile } : {}),\n ...(acceptance.storageFile !== undefined ? { storageFile: acceptance.storageFile } : {}),\n ...(acceptance.appId !== undefined ? { appId: acceptance.appId } : {}),\n skillDir: path.join(skillsRoot, safeName),\n };\n}\n\nasync function collectFromDir(skillsRoot: string, source: CollectionSource, workspaceRoot: string): Promise<LoadedCollection[]> {\n let entries: string[];\n try {\n entries = await readdir(skillsRoot);\n } catch (err) {\n if (isErrorWithCode(err) && err.code === \"ENOENT\") return [];\n log.warn(\"collections\", \"failed to list skills dir, returning empty\", { root: skillsRoot, error: String(err) });\n return [];\n }\n\n const results: LoadedCollection[] = [];\n for (const name of entries) {\n if (name.startsWith(\".\")) continue;\n const safeName = safeSlugName(name);\n if (safeName === null) continue;\n const dirPath = path.join(skillsRoot, safeName);\n let dirStat;\n try {\n dirStat = await stat(dirPath);\n } catch {\n continue;\n }\n if (!dirStat.isDirectory()) continue;\n const collection = await loadOneCollection(skillsRoot, safeName, source, workspaceRoot);\n if (collection) results.push(collection);\n }\n return results;\n}\n\n/** The user-scope dir this call should scan, or `null` for none. The single\n * place the \"explicit override beats the host binding, and either may say\n * none\" rule is spelled — `??` cannot express it, because `undefined` there\n * means \"ask the host\" and would silently re-enable a scope the caller\n * passed `null` to switch off. */\nfunction resolveUserDir(opts: DiscoveryOptions, workspaceRoot: string): string | null {\n return opts.userSkillsDir !== undefined ? opts.userSkillsDir : userSkillsDir(workspaceRoot);\n}\n\nexport interface DiscoveryOptions {\n /** Override the workspace root for project-scope skill discovery.\n * Default: the live `workspacePath`. Tests point this at a\n * `mkdtempSync` tree so they don't touch the user's real\n * `~/mulmoclaude/`. Mirrors the pattern in\n * `server/workspace/skills/catalog.ts#CatalogOptions`. */\n workspaceRoot?: string | undefined;\n /** Override `~/.claude/skills/` for tests. Production callers\n * leave this unset. Without an override, even a test-scoped\n * workspaceRoot still scans the real user home — which can leak\n * unrelated skills into the result.\n *\n * Three distinct values, and the difference matters — a caller that\n * thinks it opted out and did not is exactly the failure the scope\n * isolation removes:\n * - `undefined` (or absent): ask the host binding for this root, which\n * may itself answer `null`.\n * - a path: scan that dir as user scope.\n * - `null`: this call has NO user scope. The host is not consulted. */\n userSkillsDir?: string | null | undefined;\n}\n\n/** Discover every schema-driven collection available to this\n * workspace. Project-scope collections override user-scope on slug\n * collision. The `workspaceRoot` override also flows into each\n * collection's dataDir resolution so a tmpdir-scoped test gets\n * dataDirs under the same tmpdir (Codex P1 review on PR #1489 —\n * previously dataDir was always rooted at the live workspacePath\n * regardless of override). */\nexport async function discoverCollections(opts: DiscoveryOptions = {}): Promise<LoadedCollection[]> {\n const workspaceRoot = opts.workspaceRoot ?? getWorkspaceRoot();\n const userDir = resolveUserDir(opts, workspaceRoot);\n const projectDir = projectSkillsDir(workspaceRoot);\n // Feeds (the non-skill `<workspace>/feeds/` registry) are scanned as a\n // third root. They merge FIRST so a real skill collection (user or\n // project) always overrides a feed on slug collision — a feed must\n // never shadow a genuine skill-backed collection.\n const feedCollections = await collectFromDir(feedsRoot(workspaceRoot), \"feed\", workspaceRoot);\n // A root with no user scope skips the pass entirely (not an empty dir scan)\n // — see the `userSkillsDir` contract in `host.ts`.\n const userCollections = userDir === null ? [] : await collectFromDir(userDir, \"user\", workspaceRoot);\n const projectCollections = await collectFromDir(projectDir, \"project\", workspaceRoot);\n const merged = new Map<string, LoadedCollection>();\n for (const entry of feedCollections) merged.set(entry.slug, entry);\n for (const entry of userCollections) merged.set(entry.slug, entry);\n for (const entry of projectCollections) merged.set(entry.slug, entry);\n return [...merged.values()].sort((left, right) => left.slug.localeCompare(right.slug));\n}\n\n/** Load one collection by slug. Returns null if the slug is invalid,\n * no matching skill exists, or the schema is malformed. */\nexport async function loadCollection(slug: string, opts: DiscoveryOptions = {}): Promise<LoadedCollection | null> {\n const safeName = safeSlugName(slug);\n if (safeName === null) return null;\n const workspaceRoot = opts.workspaceRoot ?? getWorkspaceRoot();\n const userDir = resolveUserDir(opts, workspaceRoot);\n const projectDir = projectSkillsDir(workspaceRoot);\n // Project first (overrides user), then user, then the feeds registry\n // last — mirroring the merge precedence in `discoverCollections` so a\n // skill collection always wins over a feed of the same slug.\n const projectCollection = await loadOneCollection(projectDir, safeName, \"project\", workspaceRoot);\n if (projectCollection) return projectCollection;\n // No user scope for this root: skip the fallback, so a slug that exists\n // ONLY in user scope is a MISS rather than a quiet hop into another world.\n const userCollection = userDir === null ? null : await loadOneCollection(userDir, safeName, \"user\", workspaceRoot);\n if (userCollection) return userCollection;\n return loadOneCollection(feedsRoot(workspaceRoot), safeName, \"feed\", workspaceRoot);\n}\n\nexport function toSummary(collection: LoadedCollection): CollectionSummary {\n return {\n slug: collection.slug,\n title: collection.schema.title,\n icon: collection.schema.icon,\n source: collection.source,\n ...(collection.schema.dataSource !== undefined ? { readonly: true as const } : {}),\n ...(collection.appId !== undefined ? { appId: collection.appId } : {}),\n };\n}\n\nexport function toDetail(collection: LoadedCollection): CollectionDetail {\n return { ...toSummary(collection), schema: collection.schema };\n}\n"],"mappings":"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"}