{"version":3,"file":"discovery-2TvulIz8.cjs","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  return isUnderRealRoot(ancestorReal, rootReal);\n}\n\n/** The containment comparison itself, on two paths a caller has ALREADY\n *  canonicalised. Exported because a caller holding a resolved path must not\n *  re-derive it: `isContainedInRoot` canonicalises with `realpathSync`, and a\n *  path resolved by a DIFFERENT api can be a different string for the same\n *  file — on Windows `fs/promises.realpath` expands an 8.3 short name that\n *  `realpathSync` leaves alone, so `C:\\Users\\runneradmin\\…` was compared\n *  against `C:\\Users\\RUNNER~1\\…` and every file under the workspace looked\n *  like it was outside it (#2972). Canonicalise both sides the same way, then\n *  come here. */\nexport function isUnderRealRoot(realPath: string, realRoot: string): boolean {\n  if (realPath === realRoot) return true;\n  return realPath.startsWith(realRoot + 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\";\n// `Timestamp` is the one SDK VALUE this module needs: the write half of the\n// server-time codec has to hand Firestore its own type back. Importing the\n// class costs nothing at runtime (no app, no connection) and keeps the codec\n// itself free of the SDK, which matters because it also runs in a browser.\nimport { Timestamp } from \"firebase/firestore\";\n\nimport type { CollectionItem, CollectionSchema } from \"../core/schema\";\nimport { decodeRecordTimes, encodeRecordTimes } from \"../core/serverTime\";\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, schema: CollectionSchema): CollectionItem | null {\n  // A `datetime` field of a SHARED collection can be pinned to the server's\n  // clock — the rules require `request.time` there — so what comes back is a\n  // Firestore timestamp, not a string. It must not leave this store as one.\n  //\n  // Above here everything assumes the DSL's string: the record lint, the\n  // migration gate, the table, and a page — which is handed its records by\n  // structured clone or as JSON, both of which reduce a timestamp to an object\n  // whose `String()` is `\"[object Object]\"`. A page sorting by that field then\n  // compares every row equal and keeps the order it was read in, which is how\n  // the bundled first-come template came to rank its queue by document id.\n  //\n  // HERE rather than in `firestoreDocs`, because the conversion is scoped by\n  // the SCHEMA and that seam does not have one. See `../core/serverTime`.\n  return isRecord(data) ? { ...decodeRecordTimes(data, schema), [schema.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, schema: CollectionSchema): 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, schema)).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, schema: CollectionSchema, opts: ListOptions): Promise<ListPage> {\n  const items = await firestoreList(key, schema);\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, schema.primaryKey), total: items.length, truncated: false };\n}\n\nasync function firestoreRead(key: SharedCollectionKey, itemId: string, schema: CollectionSchema): 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, schema),\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\n/** The write half of the decode above, and it is not optional.\n *\n *  A record is read, edited and written back WHOLE — `putItems`' upsert\n *  replaces the document — so a decoded stamp would return to Firestore as a\n *  string. The rules freeze that field by comparing `diff().affectedKeys()`, so\n *  that write is refused and so is every later one: the record becomes\n *  permanently unupdatable. Dropping the key instead does not help, because a\n *  removed key is an affected key too.\n *\n *  Only a `datetime` field carrying the exact canonical form is converted, and\n *  that form is only ever produced by the decode — so the pair is closed over\n *  its own output and cannot re-type a value somebody else wrote. */\nasync function firestoreWrite(\n  key: SharedCollectionKey,\n  itemId: string,\n  item: CollectionItem,\n  schema: CollectionSchema,\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      const asTimestamp = (parts: { seconds: number; nanoseconds: number }): unknown => new Timestamp(parts.seconds, parts.nanoseconds);\n      if (opts.refuseOverwrite) {\n        // Nothing is stored yet, so there is no instant to preserve — and the\n        // rules make a created stamp equal `request.time`, which no client can\n        // construct, so one cannot arrive this way either.\n        const created = await guarded(key, email, () => docs.create(collectionPath, safeId, encodeRecordTimes(item, null, asTimestamp)));\n        if (!created) return { kind: \"conflict\", itemId: safeId };\n      } else {\n        // THE STORED DOCUMENT IS READ FIRST, and it is the only place the\n        // provenance survives: a decoded stamp comes back as a plain string,\n        // indistinguishable from one an author typed. Reading answers the\n        // question that matters — was this field an instant before? — so the\n        // frozen stamp goes back unchanged while a `datetime` that merely holds\n        // a canonical-looking string stays a string.\n        //\n        // One extra round trip per update of a shared record. Paid knowingly:\n        // the alternative is guessing from the format, and a wrong guess\n        // silently changes the stored type of somebody's data.\n        const previous = await guarded(key, email, () => docs.get(collectionPath, safeId));\n        const stored = encodeRecordTimes(item, isRecord(previous) ? previous : null, asTimestamp);\n        await guarded(key, email, () => docs.set(collectionPath, safeId, stored));\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 { schema } = collection;\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), schema),\n    page: async (pageOpts = {}) => firestorePage(keyOf(collection), schema, pageOpts),\n    read: async (itemId: string) => firestoreRead(keyOf(collection), itemId, schema),\n    write: async (itemId: string, item: CollectionItem, writeOpts: WriteOptions = {}) =>\n      firestoreWrite(keyOf(collection), itemId, item, schema, { ...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 { ACCENT_COLORS } from \"./accentColor\";\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  // Optional accent colour, drawn as a pale chip behind the launcher glyph so\n  // collections sharing a generic icon stay distinguishable (#2987).\n  //\n  // `.catch(undefined)` is load-bearing, not defensive dressing: discovery\n  // treats a schema that fails validation as NO COLLECTION AT ALL\n  // (`safeParse` fails -> `return null`, the row vanishes from every index).\n  // A bare `z.enum(...).optional()` would therefore make one mistyped colour\n  // cost the user their whole collection — a catastrophic price for a\n  // cosmetic field. Catching normalises the unknown value away instead: the\n  // key is dropped, the collection loads, and the launcher draws it unstyled.\n  color: z.enum(ACCENT_COLORS).optional().catch(undefined),\n  // A Material Symbols ligature name, or a single emoji. Deliberately not\n  // pattern-constrained: every renderer goes through `resolveIconGlyph`\n  // (core/iconGlyph.ts), which decides which of the two a value is and cuts\n  // anything else down to one grapheme — so a bad value degrades to a plain\n  // character instead of being rejected at write time.\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    ...(collection.schema.color !== undefined ? { color: collection.schema.color } : {}),\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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"}