{"version":3,"file":"index.cjs","names":[],"sources":["../../src/collection-watchers/config.ts","../../src/collection-watchers/clock.ts","../../src/collection-watchers/reconciler.ts","../../src/collection-watchers/watcher.ts"],"sourcesContent":["// Host-injected configuration for the collection-completion watchers.\n// The reconciler logic + watcher plumbing are host-agnostic; the\n// notification TAXONOMY (which plugin namespace, how a record priority\n// maps to a bell severity) and the in-app ROUTING (the deep-link a bell\n// row navigates to) are host-specific, so the host supplies them via an\n// adapter. MulmoClaude wires its legacy notification machinery; a future\n// MulmoTerminal wires its own routes + pluginData shape.\n\nimport type { MinimalLogger } from \"@mulmoclaude/common\";\nimport type { NotifierSeverity } from \"../notifier\";\n\n/** Two-level urgency a pending record can carry, derived from the\n *  schema's `notifyWhen` spec. The host maps this onto its own severity\n *  scale via `priorityToSeverity`. */\nexport type CompletionPriority = \"normal\" | \"high\";\n\nexport type CollectionWatcherLogger = Pick<MinimalLogger, \"info\" | \"warn\">;\n\n/** The host-specific notification surface the reconciler binds to. The\n *  reconciler owns the internal `legacyId` key (it encodes slug+itemId\n *  and round-trips it through `pluginData`); the adapter only wraps /\n *  unwraps it into whatever shape the host's bell expects. */\nexport interface CollectionNotificationAdapter {\n  /** Plugin namespace these bell entries publish under (MulmoClaude: \"todo\"). */\n  pluginPkg: string;\n  /** Map a record's completion priority onto the host's bell severity. */\n  priorityToSeverity: (priority: CompletionPriority) => NotifierSeverity;\n  /** Build the in-app deep-link the bell row routes to on click.\n   *\n   *  `root` is the absolute project/workspace root the record lives under, and\n   *  is present ONLY when the engine call carried an explicit `workspaceRoot`\n   *  — so a single-workspace host never sees it and its targets are unchanged.\n   *  A multi-root host uses it to deep-link into the RIGHT project: two roots\n   *  can own the same slug, and a slug-only target would open whichever project\n   *  happens to be selected. The URL shape is entirely the host's — the engine\n   *  only guarantees to pass the root through. Resolve it to an OPAQUE project\n   *  id in the target rather than embedding the absolute path. */\n  buildNavigateTarget: (slug: string, itemId: string, root?: string) => string;\n  /** Wrap the reconciler's internal key + priority into the host's\n   *  `pluginData` shape. Stored verbatim on the entry; recovered via\n   *  `readEntry`. */\n  buildPluginData: (input: {\n    legacyId: string;\n    slug: string;\n    itemId: string;\n    priority: CompletionPriority;\n    navigateTarget: string;\n    /** The record's root, present only under an explicit-root call (see\n     *  `buildNavigateTarget`). `legacyId` already encodes it; this is the\n     *  parsed form so a host doesn't have to decode the key. */\n    root?: string | undefined;\n  }) => unknown;\n  /** Recognise a bell entry produced by this reconciler and recover its\n   *  internal key + stored priority. Returns null for entries that didn't\n   *  originate here, so `listAll()` scans skip foreign entries. */\n  readEntry: (pluginData: unknown) => { legacyId: string; priority: CompletionPriority } | null;\n}\n\nconst NOOP_LOG: CollectionWatcherLogger = { info: () => {}, warn: () => {} };\n\nlet adapter: CollectionNotificationAdapter | null = null;\nlet activeLogger: CollectionWatcherLogger = NOOP_LOG;\n\n/** Wire the host adapter + logger. Call once at startup, before\n *  `startCollectionWatchers` or any direct reconcile call. */\nexport function configureCollectionWatchers(config: { adapter: CollectionNotificationAdapter; log?: CollectionWatcherLogger }): void {\n  ({ adapter } = config);\n  activeLogger = config.log ?? NOOP_LOG;\n}\n\nexport function requireAdapter(): CollectionNotificationAdapter {\n  if (!adapter) throw new Error(\"collection-watchers: configureCollectionWatchers() not called\");\n  return adapter;\n}\n\nexport function log(): CollectionWatcherLogger {\n  return activeLogger;\n}\n\n/** Test-only: clear the host wiring. */\nexport function resetCollectionWatchersConfig(): void {\n  adapter = null;\n  activeLogger = NOOP_LOG;\n}\n\nexport function errMsg(err: unknown): string {\n  return err instanceof Error ? err.message : String(err);\n}\n","// Evaluation-only injectable clock. When MULMOCLAUDE_FAKE_NOW is set to\n// a parseable date/datetime string, every reconcile pass derives \"now\"\n// from it instead of the wall clock — letting evaluation runs and tests\n// advance time deterministically (a `triggerField` coming due, a `spawn`\n// successor's own trigger firing later) without waiting for real days to\n// pass. Unset or unparseable (i.e. normal operation) → real wall clock.\n// Read per call, so a long-lived server picks up changes without restart.\nexport function evalNow(): Date {\n  const raw = process.env.MULMOCLAUDE_FAKE_NOW;\n  if (!raw) return new Date();\n  const parsed = new Date(raw);\n  return Number.isNaN(parsed.getTime()) ? new Date() : parsed;\n}\n","// Bell-notification reconciler for collections whose schema declares\n// `completionField`. Driven by `watcher.ts`, which calls into the\n// functions below on file-system events and on boot.\n//\n// The model is **convergent**: a watcher event re-reads the record from\n// disk and the reconciler enforces the invariant\n//\n//   bell entry exists for (slug, itemId)  ↔\n//     schema has completionField  ∧\n//     file exists  ∧\n//     `String(item[completionField])` ∉ completionDoneValues\n//     (∧ trigger due ∧ notifyWhen matches, when declared)\n//\n// Each reconcile is idempotent (`ensure*` / `clear*` no-op when state\n// already matches). This is why event-type quirks of `fs.watch`\n// (`rename` vs `change`, missed events, atomic-write coalescence) don't\n// matter — every event re-derives the desired state from the file.\n//\n// Lookup uses a deterministic internal `legacyId` derived from\n// `<slug>:<itemId>`, stashed on each entry's `pluginData` via the host\n// adapter, so the clear path and the dedup check both find the entry\n// without a side state file.\n\nimport { clear as notifierClear, listAll, publish as notifierPublish, updateForPlugin as notifierUpdate, type NotifierEntry } from \"../notifier\";\nimport { fieldText, isValidCollectionName, itemIsDone, whenMatches, type CollectionItem, type CollectionSchema } from \"../collection\";\nimport {\n  canonicalRoot,\n  type DiscoveryOptions,\n  type IoOptions,\n  isTriggerDue,\n  maybeSpawnSuccessor,\n  loadCollection,\n  storeFor,\n  type LoadedCollection,\n} from \"../collection/server\";\nimport { type CompletionPriority, errMsg, log, requireAdapter } from \"./config.js\";\nimport { evalNow } from \"./clock.js\";\n\n/** The internal-id prefix every collection-completion bell entry carries.\n *  Used both to build new keys and to filter sweep candidates from the\n *  active bell. */\nconst LEGACY_ID_PREFIX = \"collection-completion:\";\n\n/** Marks a key that carries a root. Placed FIRST so a rooted key can never be\n *  mistaken for a slug — `<prefix>@<root>\\0<slug>:<itemId>` vs the original\n *  `<prefix><slug>:<itemId>`. NUL cannot occur in a path, a slug or an itemId,\n *  so the split is unambiguous. */\nconst ROOT_MARK = \"@\";\nconst ROOT_SEP = \"\\0\";\n\n/** Marks a key that belongs to a SHARED collection (`apps/{aid}/collections/\n *  {cid}`) rather than to a directory: `<prefix>#<aid>\\0<cid>:<itemId>`. A\n *  shared collection has no root, and its `cid` may well be the slug some\n *  project already owns, so it needs a mark of its own — reusing the rootless\n *  form would make the two dedupe into one bell. */\nconst SHARED_MARK = \"#\";\n\n/** Stable key encoding (root, slug, item), round-tripped through the entry's\n *  `pluginData` so we can find it later without a side state file. Slug +\n *  itemId are upstream-validated via `safeSlugName`, which forbids the\n *  colon separator, so the two-segment parse below is unambiguous.\n *\n *  COMPATIBILITY RULE — read this before changing the format. Both apps\n *  (MulmoClaude and MulmoTerminal) read ONE notifier file,\n *  `<ws>/data/notifier/active.json`; they never run simultaneously, but an id\n *  written by one must not produce a duplicate bell for the other. So:\n *\n *  - with NO root, the id is byte-identical to the pre-multi-root format, and a\n *    single-workspace host's existing `active.json` entries keep matching;\n *  - a root is included ONLY when one was supplied, which is what lets two\n *    roots owning the same slug hold two distinct bells instead of deduping\n *    into each other;\n *  - `parseCompletionLegacyId` accepts BOTH forms, forever.\n *\n *  The root is canonicalised here as well as at the watcher's claim, because\n *  this id is written to disk: a caller reaching `reconcileItem` directly with\n *  `/proj/` where the watcher used `/proj` would otherwise publish a SECOND\n *  bell for the same record, and neither pass would ever clear the other's. */\n/** Exported for the format pin in `test/collection-watchers/test_completionId.ts`.\n *  Deliberately NOT on the package barrel: the id is a disk format, not an API. */\nexport function completionLegacyId(slug: string, itemId: string, root?: string, aid?: string): string {\n  // The two scopes are mutually exclusive, and silently preferring one is the\n  // ambiguity `collectionChangeKey` refuses for the same reason: a local bell\n  // written into the shared namespace is SKIPPED by every root's sweep, so it\n  // can never be cleared -- and nothing anywhere would say why.\n  if (root !== undefined && aid !== undefined) {\n    throw new Error(`completionLegacyId: \"${slug}\" carries both a root (${root}) and an app (${aid})`);\n  }\n  // Both names go through the SAME rule `CollectionKey` applies, because this\n  // function takes raw strings and is reachable by a caller that never built a\n  // key. Two ways this id breaks otherwise, and both are silent:\n  //   - the parse splits at the FIRST colon (which is what lets an itemId carry\n  //     one -- a timestamp, a natural key), so a name of `sales:2026` decodes\n  //     as slug `sales` with itemId `2026:<id>` and the sweep judges a live\n  //     bell against the WRONG collection and clears it;\n  //   - a NUL in the app id splits the scope early, so `salon\\0other` writes an\n  //     id that reads back as aid `salon`, slug `other\\0tasks`.\n  // A malformed shared bell is also unretirable: every root's sweep skips a\n  // parsed `aid`, so nothing cleans it up.\n  if (!isValidCollectionName(slug)) {\n    throw new Error(`completionLegacyId: collection name \"${slug}\" is not a valid collection name`);\n  }\n  if (aid !== undefined && !isValidCollectionName(aid)) {\n    throw new Error(`completionLegacyId: app id \"${aid}\" is not a valid collection name`);\n  }\n  const scope = aid !== undefined ? `${SHARED_MARK}${aid}${ROOT_SEP}` : root === undefined ? \"\" : `${ROOT_MARK}${canonicalRoot(root)}${ROOT_SEP}`;\n  return `${LEGACY_ID_PREFIX}${scope}${slug}:${itemId}`;\n}\n\n/** Decode a key back into its (root, slug, itemId), or null if the string\n *  didn't originate from this module. Accepts both the rooted and the\n *  original root-less form — see the compatibility rule above. Used by the\n *  sweep step. */\n/** @see completionLegacyId — exported for the same reason. */\nexport function parseCompletionLegacyId(legacyId: string): { root?: string; aid?: string; slug: string; itemId: string } | null {\n  if (!legacyId.startsWith(LEGACY_ID_PREFIX)) return null;\n  let body = legacyId.slice(LEGACY_ID_PREFIX.length);\n  let root: string | undefined;\n  let aid: string | undefined;\n  if (body.startsWith(ROOT_MARK) || body.startsWith(SHARED_MARK)) {\n    const shared = body.startsWith(SHARED_MARK);\n    const sep = body.indexOf(ROOT_SEP);\n    if (sep < 0) return null;\n    const scope = body.slice(1, sep);\n    // Canonicalised on the way OUT as well as on the way in. Every id this\n    // module writes is already canonical, so for its own output this is a\n    // no-op -- but the file is read back from disk, and an entry written by\n    // hand or by an older build with a trailing separator would otherwise\n    // never equal the sweep's canonical root: the verdict would be \"another\n    // root's, skip\" and the stale bell could never be cleared by anyone.\n    if (shared) aid = scope;\n    else root = canonicalRoot(scope);\n    body = body.slice(sep + ROOT_SEP.length);\n  }\n  const colon = body.indexOf(\":\");\n  if (colon < 0) return null;\n  const parsed = { slug: body.slice(0, colon), itemId: body.slice(colon + 1) };\n  if (aid !== undefined) return { aid, ...parsed };\n  return root === undefined ? parsed : { root, ...parsed };\n}\n\n/** The human-readable label shown in a completion notification's title.\n *  Uses the schema's `displayField` value when declared and non-empty;\n *  otherwise falls back to the record's primaryKey (`itemId`). */\nexport function resolveDisplayLabel(schema: CollectionSchema, item: CollectionItem, itemId: string): string {\n  const { displayField } = schema;\n  if (!displayField) return itemId;\n  // Same rule as `itemLabelOf`: an array/object display field falls back to\n  // the id rather than labelling the record \"[object Object]\".\n  const label = fieldText(item[displayField]).trim();\n  return label.length > 0 ? label : itemId;\n}\n\n// The done predicate moved to `../collection/core/completion` (flag-aware,\n// browser-safe, shared with spawn and view filters). Re-exported so the\n// `collection-watchers` barrel keeps its public surface (MulmoTerminal).\nexport { itemIsDone };\n\n/** Every active bell entry whose key matches this (slug, itemId).\n *  Returns multiple when defensive cleanup is needed. Scans `listAll()`\n *  — cheap because the active set is bounded. */\nasync function findActiveEntries(slug: string, itemId: string, root: string | undefined): Promise<NotifierEntry[]> {\n  const adapter = requireAdapter();\n  const legacyId = completionLegacyId(slug, itemId, root);\n  const entries = await listAll();\n  return entries.filter((entry) => adapter.readEntry(entry.pluginData)?.legacyId === legacyId);\n}\n\nasync function findActiveEntryIds(slug: string, itemId: string, root: string | undefined): Promise<string[]> {\n  return (await findActiveEntries(slug, itemId, root)).map((entry) => entry.id);\n}\n\n/** Per-key in-flight lock. Serializes concurrent `ensureItemNotification`\n *  calls for the same (slug, itemId) so the `findActiveEntries → publish`\n *  check stays atomic across callers — not just across watcher events.\n *  `listAll` bypasses the engine's write queue, so without this lock two\n *  reconcile paths (a watcher event + a `reconcileAllItems` pass that a\n *  readdir-triggered event raced) could both miss each other's in-flight\n *  publish and produce duplicate entries. */\ninterface EnsureLock {\n  promise: Promise<void>;\n}\nconst ensureLocks = new Map<string, EnsureLock>();\n\n/** Bell priority for a record: the FIRST flagged value in `notifyWhen.in`\n *  (most urgent) reads `high`, every other flagged value `normal`.\n *  Collections with no `notifyWhen` (notify for every open record) stay\n *  `normal`. */\nfunction notifyPriorityForItem(schema: CollectionSchema, item: CollectionItem): CompletionPriority {\n  const spec = schema.notifyWhen;\n  if (!spec) return \"normal\";\n  const value = item[spec.field] === undefined || item[spec.field] === null ? \"\" : String(item[spec.field]);\n  return spec.in.indexOf(value) === 0 ? \"high\" : \"normal\";\n}\n\nasync function ensureItemNotification(\n  slug: string,\n  schema: CollectionSchema,\n  itemId: string,\n  displayLabel: string,\n  priority: CompletionPriority,\n  root: string | undefined,\n): Promise<void> {\n  const legacyId = completionLegacyId(slug, itemId, root);\n  // Drain any in-flight publish for this key BEFORE our check + set. The\n  // drain + claim runs synchronously between `ensureLocks.get` and\n  // `ensureLocks.set`, so two callers can't both observe an empty slot.\n\n  while (true) {\n    const inflight = ensureLocks.get(legacyId);\n    if (!inflight) break;\n    await inflight.promise;\n  }\n  const lock: EnsureLock = { promise: doEnsureItemNotification({ slug, schema, itemId, legacyId, displayLabel, priority, root }) };\n  ensureLocks.set(legacyId, lock);\n  try {\n    await lock.promise;\n  } finally {\n    // Only clear the slot if it still points at OUR lock — a\n    // sufficiently-delayed cleanup must not stomp a later claim.\n    if (ensureLocks.get(legacyId) === lock) {\n      ensureLocks.delete(legacyId);\n    }\n  }\n}\n\n/** Converge any already-present bell entries to `priority`, updating in\n *  place (preserving id / position / createdAt) so a record whose flagged\n *  value changed while it stayed pending re-colours the bell without a\n *  clear+republish flicker. No-op when the stored priority already matches. */\nasync function reconcileEntrySeverity(\n  slug: string,\n  itemId: string,\n  entries: NotifierEntry[],\n  priority: CompletionPriority,\n  root: string | undefined,\n): Promise<void> {\n  const adapter = requireAdapter();\n  for (const entry of entries) {\n    const parsed = adapter.readEntry(entry.pluginData);\n    if (!parsed || parsed.priority === priority) continue;\n    await notifierUpdate(adapter.pluginPkg, entry.id, {\n      severity: adapter.priorityToSeverity(priority),\n      pluginData: adapter.buildPluginData({\n        legacyId: parsed.legacyId,\n        slug,\n        itemId,\n        priority,\n        navigateTarget: adapter.buildNavigateTarget(slug, itemId, root),\n        root,\n      }),\n    });\n  }\n}\n\ninterface EnsureInput {\n  slug: string;\n  schema: CollectionSchema;\n  itemId: string;\n  legacyId: string;\n  displayLabel: string;\n  priority: CompletionPriority;\n  root: string | undefined;\n}\n\nasync function doEnsureItemNotification({ slug, schema, itemId, legacyId, displayLabel, priority, root }: EnsureInput): Promise<void> {\n  const adapter = requireAdapter();\n  try {\n    const existing = await findActiveEntries(slug, itemId, root);\n    if (existing.length > 0) {\n      await reconcileEntrySeverity(slug, itemId, existing, priority, root);\n      return;\n    }\n    const navigateTarget = adapter.buildNavigateTarget(slug, itemId, root);\n    // `lifecycle: \"action\"` — these are state-of-the-world entries\n    // mirroring an outstanding obligation (the item is pending), not\n    // transient pings. Validation requires a non-info severity and a\n    // non-empty `navigateTarget` (the slug + itemId deep-link).\n    await notifierPublish({\n      pluginPkg: adapter.pluginPkg,\n      severity: adapter.priorityToSeverity(priority),\n      lifecycle: \"action\",\n      title: `${schema.title}: ${displayLabel}`,\n      navigateTarget,\n      pluginData: adapter.buildPluginData({ legacyId, slug, itemId, priority, navigateTarget, root }),\n    });\n  } catch (err) {\n    log().warn(\"notify ensure failed\", { slug, itemId, error: errMsg(err) });\n  }\n}\n\n/** Idempotently clear EVERY bell entry that matches this (slug, itemId).\n *  Silent no-op when nothing matches. The \"every\" is defensive: if a\n *  duplicate ever slips through, this drains the lot. */\nexport async function clearItemNotification(slug: string, itemId: string, root?: string): Promise<void> {\n  try {\n    const ids = await findActiveEntryIds(slug, itemId, root);\n    for (const entryId of ids) {\n      await notifierClear(entryId);\n    }\n  } catch (err) {\n    log().warn(\"notify clear failed\", { slug, itemId, error: errMsg(err) });\n  }\n}\n\n/** Reconcile one item to the desired bell state. Re-reads the record\n *  through the collection's STORE (file, sqlite, …) so the decision is\n *  grounded in current truth, not in the event payload. Safe to call when\n *  the record is missing (delete path).\n *\n *  `ioOpts` flows into the store's workspace-containment checks —\n *  production callers (the watcher) pass nothing; tests pass\n *  `{ workspaceRoot: <tmpdir> }` so the check accepts a fixture dataDir. */\nexport async function reconcileItem(collection: LoadedCollection, itemId: string, ioOpts: IoOptions = {}, now: Date = evalNow()): Promise<void> {\n  const { slug, schema } = collection;\n  // The bell's root follows the same rule as the change payload's: present\n  // only when THIS call carried an explicit `workspaceRoot`, so a\n  // single-workspace host's ids stay byte-identical to what it already wrote.\n  const root = ioOpts.workspaceRoot;\n  if (!schema.completionField) {\n    // Schema doesn't track completion — drop any stale entry.\n    await clearItemNotification(slug, itemId, root);\n    return;\n  }\n  const item = await storeFor(collection, ioOpts).read(itemId);\n  if (item === null) {\n    await clearItemNotification(slug, itemId, root);\n    return;\n  }\n  // Recurrence: predicate-gated + create-if-absent, idempotent and\n  // independent of this item's own bell state. Runs before the done-clear\n  // below so marking an item done still spawns its successor.\n  await maybeSpawnSuccessor(collection, item, itemId, ioOpts);\n  if (itemIsDone(schema, item)) {\n    await clearItemNotification(slug, itemId, root);\n    return;\n  }\n  // Time gate: when the schema declares `triggerField`, suppress the bell\n  // until the clock reaches that date (minus `triggerLeadDays`).\n  // Unparseable date ⇒ fail safe (no bell); warn ONLY when the field carries\n  // a non-empty value that won't parse — an empty optional trigger date is a\n  // normal state and must not spam a WARN every reconcile tick.\n  if (schema.triggerField) {\n    const triggerRaw = item[schema.triggerField];\n    const due = isTriggerDue(triggerRaw, now, schema.triggerLeadDays);\n    const isEmpty = triggerRaw === undefined || triggerRaw === null || triggerRaw === \"\";\n    if (due === null && !isEmpty) {\n      log().warn(\"trigger date unparseable, suppressing bell\", { slug, itemId, triggerField: schema.triggerField });\n    }\n    if (due !== true) {\n      await clearItemNotification(slug, itemId, root);\n      return;\n    }\n  }\n  // Condition gate: when the schema declares `notifyWhen`, only bell\n  // records matching the predicate. Convergent — a record that stops\n  // matching has its bell cleared.\n  if (!whenMatches(schema.notifyWhen, item)) {\n    await clearItemNotification(slug, itemId, root);\n    return;\n  }\n  await ensureItemNotification(slug, schema, itemId, resolveDisplayLabel(schema, item, itemId), notifyPriorityForItem(schema, item), root);\n}\n\n/** Boot-time reconcile: walk every record of the collection once (through\n *  its store) and reconcile it. Catches up changes that happened while the\n *  server was down. Deleted items are covered by\n *  `sweepStaleActiveEntries`, not this function (it only sees records\n *  that exist). */\nexport async function reconcileAllItems(collection: LoadedCollection, ioOpts: IoOptions = {}, now: Date = evalNow()): Promise<void> {\n  const { slug, schema } = collection;\n  if (!schema.completionField) return;\n  let items: CollectionItem[];\n  try {\n    items = await storeFor(collection, ioOpts).list();\n  } catch (err) {\n    log().warn(\"reconcile list failed\", { slug, error: errMsg(err) });\n    return;\n  }\n  const { primaryKey } = schema;\n  for (const item of items) {\n    const raw = item[primaryKey];\n    if (typeof raw !== \"string\" || raw.length === 0) continue;\n    await reconcileItem(collection, raw, ioOpts, now);\n  }\n}\n\n/** Boot-time sweep over the active bell: drop any entries whose underlying\n *  file is gone, whose collection was deleted, whose schema no longer\n *  tracks completion, or whose item is now done. Reverse-covers the cases\n *  `reconcileAllItems` misses (it only walks files that exist). */\n/** What this sweep should do with an entry, given the root it belongs to and\n *  the root the sweep is running for.\n *\n *  - `mine` — same root (or both root-less: the single-workspace case, which\n *    behaves exactly as it always did). Judge it against the records on disk.\n *  - `skip` — ANOTHER root's entry. Its collection and records live in a tree\n *    this sweep cannot see, so every check would read \"gone\" and clear a\n *    perfectly live bell. Two roots share one notifier file; this is the\n *    boundary between them.\n *  - `drop-legacy` — a root-less entry seen by a sweep that HAS a root: a\n *    leftover from before bell ids carried one. Nothing this host does from now\n *    on produces a root-less id, so skipping it would strand a bell no pass can\n *    ever clear, while the reconcile publishes a second rooted one beside it.\n *    Clearing converges — the record republishes rooted if still pending. */\nfunction sweepVerdict(entry: { root?: string; aid?: string }, ownRoot: string | undefined): \"mine\" | \"skip\" | \"drop-legacy\" {\n  // A shared collection's bell belongs to no root. A root's sweep must neither\n  // claim it (`isStaleEntry` resolves the slug on DISK and would find nothing,\n  // so every shared bell would be judged stale) nor treat it as a root-less\n  // legacy entry to drop — either way it silently clears another surface's\n  // bells. Only the shared watcher may retire these.\n  if (entry.aid !== undefined) return \"skip\";\n  if (entry.root === ownRoot) return \"mine\";\n  if (entry.root === undefined) return \"drop-legacy\";\n  return \"skip\";\n}\n\n/** Should this entry's bell be dropped? True when the collection is gone, no\n *  longer tracks completion, or the record is missing / done / outside\n *  `notifyWhen`. The reverse of the reconcile invariant, applied to an entry\n *  whose record the forward pass can never walk to (a delete leaves a bell that\n *  a walk over the SURVIVING records cannot clear). */\nasync function isStaleEntry(slug: string, itemId: string, opts: DiscoveryOptions): Promise<boolean> {\n  const collection = await loadCollection(slug, opts);\n  if (!collection || !collection.schema.completionField) return true;\n  const item = await storeFor(collection, opts).read(itemId);\n  return item === null || itemIsDone(collection.schema, item) || !whenMatches(collection.schema.notifyWhen, item);\n}\n\nexport async function sweepStaleActiveEntries(opts: DiscoveryOptions = {}): Promise<void> {\n  const adapter = requireAdapter();\n  let entries;\n  try {\n    entries = await listAll();\n  } catch (err) {\n    log().warn(\"sweep list failed\", { error: errMsg(err) });\n    return;\n  }\n  const ownRoot = opts.workspaceRoot === undefined ? undefined : canonicalRoot(opts.workspaceRoot);\n  for (const entry of entries) {\n    const own = adapter.readEntry(entry.pluginData);\n    if (!own) continue;\n    const parsed = parseCompletionLegacyId(own.legacyId);\n    if (!parsed) continue;\n    const verdict = sweepVerdict(parsed, ownRoot);\n    if (verdict === \"skip\") continue;\n    if (verdict === \"drop-legacy\") {\n      await notifierClear(entry.id);\n      continue;\n    }\n    const { slug, itemId } = parsed;\n    try {\n      if (await isStaleEntry(slug, itemId, opts)) await notifierClear(entry.id);\n    } catch (err) {\n      log().warn(\"sweep entry failed\", { slug, itemId, error: errMsg(err) });\n    }\n  }\n}\n\n/** Test-only: clear the per-key in-flight locks. */\nexport function _resetReconcilerLocksForTesting(): void {\n  ensureLocks.clear();\n}\n","// Filesystem watchers that drive collection-completion bell\n// notifications AND the live-refresh change event. One `fs.watch` per\n// discovered collection's `dataDir`, fanned out from a single boot call\n// + a 30-second re-discovery interval that catches newly-created /\n// deleted collections (there is no in-process \"collections changed\"\n// event broadcast).\n//\n// Why a watcher, not just route hooks: the canonical pattern for\n// collection-skills has the agent Write records directly with the Write\n// tool — that path never hits the REST API, so a route-level hook would\n// miss most of the traffic the user generates. The watcher catches every\n// mutation regardless of who wrote the file.\n//\n// That same reasoning is why the watcher publishes change events: a\n// write through `io.ts` publishes its own (immediately, and reliably\n// even where fs.watch is unavailable), but a direct file write has no\n// other producer, so open views would never refresh. Both producers\n// firing for one `io.ts` write is intentional — the payload carries no\n// bodies, so a duplicate costs one redundant refetch, whereas a missed\n// event leaves the UI silently stale.\n//\n// FOUR paths can re-derive a collection's bells, and every backend —\n// including read-only `dataSource` — must be covered by all four. They are\n// listed because they were NOT: this module grew up when only JSON records\n// reconciled, so each path had its own `dataSource` short-circuit, and\n// routing every backend through the store contract left the short-circuits\n// behind. Three of the four were separate live bugs (PR #2243 review).\n//\n//   1. mount           — `startWatcherFor`, boot + every remount\n//   2. store change    — `handleStoreChange`, the backend reported bytes moved\n//   3. schema change   — `reconcileChangedSchemas`, the RULES moved instead\n//   4. clock tick      — `tickTimeTriggers`, `triggerField` came due\n//\n// 3 and 4 are the ones that look skippable and are not: a read-only backend's\n// rows never change, but the completion rules applied to them and the wall\n// clock both do, and neither produces a data event to react to. Before adding\n// a `dataSource` (or any per-backend) early-exit here, check it against all\n// four — the surviving ones below are view-refresh publishes, not skips.\n//\n// ONE GENERATION PER ROOT — the multi-root boundary.\n//\n// Every piece of watcher state (`watchers`, `itemSlots`, `collectionSlots`, the\n// rediscovery + trigger timers, `discoveryOpts`) lives on a `WatcherGeneration`\n// object, and `generations` holds one per root. A host may therefore watch\n// several projects at once; each generation stamps its own root on its change\n// payloads and drives its own bells.\n//\n// It could not always do that, and the reason is worth keeping: the watcher's\n// maps are keyed by slug, but so was the NOTIFICATION identity it drives —\n// `reconciler.ts` derived every bell's `legacyId` from `completionLegacyId(slug,\n// itemId)`. Two roots each owning a `tasks` collection collided on the BELL, not\n// merely on a watcher slot, so re-keying these maps alone would have traded a\n// loud failure for a quiet one. The bell identity now carries the root (and\n// omits it when there is none, so a single-workspace host's ids are unchanged),\n// which is what makes concurrency here correct rather than merely possible.\n//\n// A single-workspace host is unaffected: it starts exactly one generation, for\n// the host's configured root, and everything it observes — payload shape, bell\n// ids, `stop()` semantics — is what it saw before.\n//\n// All decisions live in `reconciler.ts`; this module is pure plumbing:\n// discover, mkdir, fs.watch, forward events into the reconciler. Every\n// reconcile call is idempotent so fs.watch's well-known quirks (`rename`\n// vs `change`, atomic-write coalescence, filename === null on some\n// platforms) don't need special handling.\n\nimport { access } from \"node:fs/promises\";\nimport {\n  canonicalRoot,\n  collectionChangePayload,\n  discoverCollections,\n  itemFilePath,\n  loadCollection,\n  peekWorkspaceRoot,\n  publishCollectionChange,\n  storeFor,\n  type CollectionChangePayload,\n  type DiscoveryOptions,\n  type LoadedCollection,\n  type StoreChange,\n} from \"../collection/server\";\nimport { errMsg, log } from \"./config.js\";\nimport { evalNow } from \"./clock.js\";\nimport { reconcileAllItems, reconcileItem, sweepStaleActiveEntries } from \"./reconciler.js\";\n\n// Collections don't get added / removed rapidly; 30 s is a comfortable\n// upper bound on how long a new schema can sit before its watcher is up.\nconst ONE_SECOND_MS = 1000;\nconst ONE_MINUTE_MS = 60 * ONE_SECOND_MS;\nconst REDISCOVERY_INTERVAL_MS = 30 * ONE_SECOND_MS;\n\n// Wall-clock tick that re-reconciles time-dependent collections (those\n// declaring `triggerField` and/or `spawn`). The fs.watcher only re-runs\n// the reconciler on FILE changes; a `triggerField` bell that should fire\n// \"when the clock reaches date X\" — and a `spawn` whose successor's own\n// trigger later comes due — change no file at that moment, so a periodic\n// re-derivation is required.\nconst TRIGGER_TICK_INTERVAL_MS = ONE_MINUTE_MS;\n\ninterface CollectionWatcher {\n  slug: string;\n  dataDir: string;\n  /** Unsubscribe from the store's change stream. The store owns HOW changes\n   *  are detected (which paths, which filenames are noise, how an atomic\n   *  replace is debounced); this module only holds the handle. */\n  unsubscribe: () => void;\n  /** Last-seen serialized schema, used ONLY as a change-detection\n   *  fingerprint. When a rediscovery tick observes a different value, the\n   *  watcher's items are reconciled and the cache is refreshed — this catches\n   *  schema-only edits (e.g. flipping `completionField` on or off) that don't\n   *  touch any record file and would otherwise leave bell state stale\n   *  indefinitely. Never parsed back: `collection.schema` below is the same\n   *  schema, already typed. */\n  schemaJson: string;\n  /** The discovered collection this watcher was mounted for — what the\n   *  reconciler needs to pick the right STORE (file records vs a sqlite\n   *  `storage` db), and the typed schema every other read here goes through.\n   *  Refreshed whenever `schemaJson` is. */\n  collection: LoadedCollection;\n}\n\n/** Per-key single-flight slot. */\ninterface ReconcileSlot {\n  running: Promise<void>;\n  pending: boolean;\n}\n\n/** One root's worth of watcher state. Everything that used to be module-global\n *  hangs off here, so two roots can be watched side by side without sharing a\n *  map, a timer or a guard. */\ninterface WatcherGeneration {\n  /** Discovery options threaded into every `discoverCollections` /\n   *  `loadCollection` / `sweepStaleActiveEntries` call, and the source of the\n   *  `root` stamped on this generation's change payloads and bell ids.\n   *  Deliberately NOT normalized: an omitted `workspaceRoot` must stay omitted,\n   *  because the payload's `root` means \"an explicit root was passed\". */\n  discoveryOpts: DiscoveryOptions;\n  watchers: Map<string, CollectionWatcher>;\n  itemSlots: Map<string, ReconcileSlot>;\n  /** Per-slug single-flight for a COLLECTION-granularity reconcile — a burst\n   *  of changes the store couldn't attribute to a record collapses into one\n   *  pass plus one trailing re-run, mirroring the per-item slots. */\n  collectionSlots: Map<string, ReconcileSlot>;\n  rediscoveryTimer: ReturnType<typeof setInterval> | null;\n  triggerTimer: ReturnType<typeof setInterval> | null;\n  started: boolean;\n  /** Guards the clock tick against overlapping itself. Paired with `alive`: a\n   *  teardown while a pass is in flight clears it, so that pass's `finally`\n   *  knows it belongs to a dead generation and must not clear a guard a\n   *  restarted generation now owns. `triggerTickInFlight` is what teardown\n   *  AWAITS — clearing the flag alone would let a restart run a second pass\n   *  alongside the first. */\n  triggerTickRunning: boolean;\n  triggerTickInFlight: Promise<void> | null;\n  /** False from teardown onwards. See `triggerTickRunning`. */\n  alive: boolean;\n  /** The boot pass, from before its first await until it settles. This is the\n   *  generation CLAIM: `started` flips only at the end of boot, so it cannot by\n   *  itself stop two concurrent starts for the same root from racing each\n   *  other's state. Teardown awaits this too, so a boot in flight can't arm its\n   *  intervals after the stop that was meant to disarm them. */\n  bootInFlight: Promise<void> | null;\n}\n\n/** Live generations, keyed by NORMALIZED root: an omitted `workspaceRoot`\n *  resolves to the host's configured default, so `start()` and\n *  `start({ workspaceRoot: <that same default> })` join one generation rather\n *  than mounting two watchers over the same tree. */\nconst generations = new Map<string, WatcherGeneration>();\n\n/** Map key for a normalized root. `\"\"` stands for \"the host has no configured\n *  default and this call named no root\" — a state in which discovery itself\n *  would throw, so at most one such generation can meaningfully exist. */\nconst generationKey = (root: string | undefined): string => root ?? \"\";\n\n/** Retained for hosts that catch it. NO LONGER THROWN: starting a second root\n *  now mounts a second generation instead of refusing. It stays exported\n *  because the refusal was a documented, catchable failure and a host's\n *  `.catch` branch for it must keep compiling — and because a future host-side\n *  constraint that reintroduces a conflict should reuse this code rather than\n *  invent a second one. */\nexport const WATCHER_ROOT_CONFLICT = \"WATCHER_ROOT_CONFLICT\";\n\n/** The root a start request resolves to: its explicit override, else the host's\n *  configured default. `peekWorkspaceRoot` rather than `getWorkspaceRoot` — this\n *  is a comparison, and under an explicit-root binding there is no default to\n *  read, which is not an error here. */\nfunction effectiveRoot(opts: DiscoveryOptions | undefined): string | undefined {\n  const root = opts?.workspaceRoot ?? peekWorkspaceRoot() ?? undefined;\n  // Canonical, because this value IS the generation's identity — `/work/proj`\n  // and `/work/proj/` must not mount two watcher sets over one tree.\n  return root === undefined ? undefined : canonicalRoot(root);\n}\n\n/** The discovery options a generation runs under, with an explicit root put in\n *  canonical form. Everything downstream — the change payload's `root`, the\n *  bell id the reconciler derives — reads it from here, so canonicalising once\n *  at the claim is what keeps those three consistent. An omitted root stays\n *  omitted: \"no explicit root\" is a distinct state, not a root to normalise. */\nfunction canonicalDiscoveryOpts(opts: DiscoveryOptions | undefined): DiscoveryOptions {\n  const base = opts ?? {};\n  return base.workspaceRoot === undefined ? base : { ...base, workspaceRoot: canonicalRoot(base.workspaceRoot) };\n}\n\nfunction newGeneration(discoveryOpts: DiscoveryOptions): WatcherGeneration {\n  return {\n    discoveryOpts,\n    watchers: new Map(),\n    itemSlots: new Map(),\n    collectionSlots: new Map(),\n    rediscoveryTimer: null,\n    triggerTimer: null,\n    started: false,\n    triggerTickRunning: false,\n    triggerTickInFlight: null,\n    alive: true,\n    bootInFlight: null,\n  };\n}\n\n/** The generation used when NO watcher is running at all. Several tests (and a\n *  host that drives a reconcile directly) call the reconcile helpers without a\n *  boot; they still need ONE stable set of single-flight slots, or a burst of\n *  calls that should coalesce into a single publish would each get their own\n *  slot. Created lazily and dropped by a full `stopCollectionWatchers()`. */\nlet detachedGeneration: WatcherGeneration | null = null;\n\nfunction fallbackGeneration(): WatcherGeneration {\n  detachedGeneration ??= newGeneration({});\n  return detachedGeneration;\n}\n\n/** The generation a test helper or a root-less call should act on: the one\n *  named by `workspaceRoot`, else the only one running, else the default\n *  root's. \"The only one running\" is what keeps the single-root test helpers\n *  (which name no root) working against a generation booted for a tmpdir. */\nfunction resolveGeneration(workspaceRoot?: string): WatcherGeneration | undefined {\n  if (workspaceRoot !== undefined) return generations.get(generationKey(workspaceRoot));\n  if (generations.size === 1) return [...generations.values()][0];\n  return generations.get(generationKey(effectiveRoot(undefined)));\n}\n\n/** Test-only configuration knobs. Production callers pass nothing and get\n *  the live workspace defaults; tests pass a tmpdir-rooted `discoveryOpts`\n *  and override the tick cadences (or set them to `null` to disable the\n *  auto-ticks so the test drives sync manually). */\nexport interface CollectionWatcherOptions {\n  discoveryOpts?: DiscoveryOptions;\n  rediscoveryIntervalMs?: number | null;\n  triggerTickIntervalMs?: number | null;\n}\n\n/** Boot entry point: sweep stale active entries, then mount watchers for\n *  every discovered collection and arm the periodic re-discovery poll.\n *  Idempotent for the SAME root — a second call joins the first.\n *\n *  A call for a DIFFERENT root mounts a SECOND generation beside the first, so\n *  a multi-root host can watch several projects at once. Each generation keeps\n *  its own watcher set, timers and single-flight slots, stamps its own root on\n *  its change payloads, and drives bells whose ids carry that root — which is\n *  what stops two projects' `tasks` collections from deduping into one bell. */\nexport async function startCollectionWatchers(opts: CollectionWatcherOptions = {}): Promise<void> {\n  // Claim the generation SYNCHRONOUSLY. `started` only flips after two awaits,\n  // so guarding on it alone lets two concurrent callers for the SAME root both\n  // pass, mount two watcher sets over one tree, and each arm their own interval\n  // — the first of which then escapes teardown, since the generation holds only\n  // the last handle. `bootInFlight` is set before the first await, so a\n  // concurrent caller sees the claim.\n  const key = generationKey(effectiveRoot(opts.discoveryOpts));\n  const existing = generations.get(key);\n  if (existing) {\n    // Join the in-flight boot rather than returning early: a caller that\n    // returned here while the first pass was still mounting would believe\n    // watchers were up before they were.\n    await existing.bootInFlight;\n    return;\n  }\n  const gen = newGeneration(canonicalDiscoveryOpts(opts.discoveryOpts));\n  generations.set(key, gen);\n  gen.bootInFlight = bootWatchers(key, gen, opts);\n  try {\n    await gen.bootInFlight;\n  } finally {\n    gen.bootInFlight = null;\n  }\n}\n\n/** The boot pass itself, owned by exactly one caller — `startCollectionWatchers`\n *  serialises entry into it. On failure it resets `discoveryOpts` so a\n *  supervisor / test harness can retry instead of being permanently latched. */\nasync function bootWatchers(key: string, gen: WatcherGeneration, opts: CollectionWatcherOptions): Promise<void> {\n  try {\n    // Boot reconcile is split in two: sweep first (drop bell entries whose\n    // files / collections / schemas vanished while the server was down),\n    // then `syncWatchers` runs the per-collection forward fill. Both paths\n    // are idempotent and converge on the same end state.\n    await sweepStaleActiveEntries(gen.discoveryOpts);\n    await syncWatchers(gen);\n    const intervalMs = opts.rediscoveryIntervalMs === undefined ? REDISCOVERY_INTERVAL_MS : opts.rediscoveryIntervalMs;\n    if (intervalMs !== null) {\n      gen.rediscoveryTimer = setInterval(() => {\n        syncWatchers(gen).catch((err: unknown) => {\n          log().warn(\"watcher rediscovery failed\", { error: errMsg(err) });\n        });\n      }, intervalMs);\n      // `unref` so a clean process exit isn't blocked waiting for the tick.\n      gen.rediscoveryTimer.unref();\n    }\n    const triggerMs = opts.triggerTickIntervalMs === undefined ? TRIGGER_TICK_INTERVAL_MS : opts.triggerTickIntervalMs;\n    if (triggerMs !== null) {\n      gen.triggerTimer = setInterval(() => {\n        // Skip rather than overlap. The pass is idempotent and the next one\n        // is a minute away, so dropping a tick is harmless — whereas letting\n        // firings pile up on a slow pass is not.\n        if (gen.triggerTickRunning) return;\n        gen.triggerTickRunning = true;\n        gen.triggerTickInFlight = tickTimeTriggers(gen)\n          .catch((err: unknown) => {\n            log().warn(\"watcher trigger tick failed\", { error: errMsg(err) });\n          })\n          .finally(() => {\n            // A teardown that ran while this pass was in flight already cleared\n            // the guard; a dead generation must not undo it.\n            if (!gen.alive) return;\n            gen.triggerTickRunning = false;\n            gen.triggerTickInFlight = null;\n          });\n      }, triggerMs);\n      gen.triggerTimer.unref();\n    }\n    gen.started = true;\n  } catch (err) {\n    // Drop the claim so a supervisor / test harness can retry instead of being\n    // permanently latched on a half-booted generation. Uses the key the CLAIM\n    // was made under, not a recomputed one: a root-less claim resolves through\n    // `peekWorkspaceRoot()`, so if the host's configured root changed in\n    // between, recomputing would delete some other generation and leave this\n    // dead one in the map — where a later start would find it, await a null\n    // `bootInFlight`, and return believing watchers were up.\n    gen.alive = false;\n    generations.delete(key);\n    throw err;\n  }\n}\n\n/** Tear down watchers and stop their intervals, releasing the root claim so a\n *  subsequent `startCollectionWatchers` may re-mount.\n *\n *  SCOPE: `{ workspaceRoot }` stops exactly that root's generation and leaves\n *  every other project running — what a multi-root host wants when it closes\n *  one project. Called with NO options it stops ALL generations, which is what\n *  it has always meant for a single-workspace host (and for a test teardown\n *  that must not leak a timer into the next file).\n *\n *  This is a production API, not a test-only one. Await it: it waits out a boot\n *  still in flight and a clock pass still running, so the next start does not\n *  race the one it replaced. */\nexport async function stopCollectionWatchers(opts?: { workspaceRoot?: string }): Promise<void> {\n  if (opts?.workspaceRoot !== undefined) {\n    const key = generationKey(effectiveRoot(opts));\n    const gen = generations.get(key);\n    if (gen) await stopGeneration(key, gen);\n    return;\n  }\n  for (const [key, gen] of [...generations.entries()]) {\n    await stopGeneration(key, gen);\n  }\n  if (opts?.workspaceRoot === undefined) detachedGeneration = null;\n}\n\nasync function stopGeneration(key: string, gen: WatcherGeneration): Promise<void> {\n  // Let a boot in flight finish first. Disarming intervals it has not armed\n  // yet would leave them running past the teardown that was meant to stop\n  // them. A failed boot is fine to swallow here — we are tearing down anyway.\n  await gen.bootInFlight?.catch(() => {});\n  if (gen.rediscoveryTimer) {\n    clearInterval(gen.rediscoveryTimer);\n    gen.rediscoveryTimer = null;\n  }\n  if (gen.triggerTimer) {\n    clearInterval(gen.triggerTimer);\n    gen.triggerTimer = null;\n  }\n  // Wait for a clock pass that is still running: the interval is disarmed\n  // above, but a pass already in flight keeps touching the notifier and the\n  // slot maps, and a restart would otherwise run a second one beside it.\n  await gen.triggerTickInFlight;\n  gen.triggerTickInFlight = null;\n  // Kill the generation AFTER the await: any later `finally` from that pass now\n  // sees a dead generation and becomes a no-op, so it can't undo this teardown.\n  gen.alive = false;\n  gen.triggerTickRunning = false;\n  for (const watcher of gen.watchers.values()) {\n    try {\n      watcher.unsubscribe();\n    } catch {\n      /* unsubscribe is best-effort */\n    }\n  }\n  gen.watchers.clear();\n  gen.itemSlots.clear();\n  gen.collectionSlots.clear();\n  gen.started = false;\n  generations.delete(key);\n}\n\n/** Test-only: manually trigger one rediscovery + reconcile pass. */\nexport async function _syncWatchersForTesting(workspaceRoot?: string): Promise<boolean> {\n  const gen = resolveGeneration(workspaceRoot);\n  return gen ? syncWatchers(gen) : false;\n}\n\n/** Test-only: drive one wall-clock tick synchronously, with an optional\n *  injected clock. */\nexport async function _tickTimeTriggersForTesting(now?: Date, workspaceRoot?: string): Promise<void> {\n  const gen = resolveGeneration(workspaceRoot);\n  if (gen) await tickTimeTriggers(gen, now);\n}\n\n/** Re-reconcile every watched collection that depends on the clock — i.e.\n *  declares `triggerField` (a bell that fires at a date) and/or `spawn`\n *  (recurrence whose successors come due over time). Collections with\n *  neither are skipped. Idempotent. Reads the watcher's cached\n *  `collection.schema` to avoid a per-tick disk read. */\nasync function tickTimeTriggers(gen: WatcherGeneration, now: Date = evalNow()): Promise<void> {\n  for (const entry of gen.watchers.values()) {\n    const { schema } = entry.collection;\n    // dataSource is NOT excluded. Its rows are read-only, but `triggerField`\n    // is not among the keys zod forbids on it, and a trigger date fires from\n    // the CLOCK — the one state change that arrives without the file moving.\n    // Skipping it here left CSV rows that were pending-but-not-yet-due unable\n    // to ever bell unless the file happened to be rewritten.\n    if (!schema.triggerField && !schema.spawn) continue;\n    await reconcileAllItems(entry.collection, gen.discoveryOpts, now);\n  }\n}\n\n/** Reconcile the watcher set against the currently-discovered\n *  collections. Adds watchers for new slugs (with a boot reconcile of\n *  their items), drops watchers for vanished slugs, and re-reconciles\n *  items for collections whose schema changed. Runs a final sweep when\n *  this tick changed the watcher set or any schema. */\nasync function syncWatchers(gen: WatcherGeneration): Promise<boolean> {\n  let collections;\n  try {\n    collections = await discoverCollections(gen.discoveryOpts);\n  } catch (err) {\n    log().warn(\"watcher discover failed\", { error: errMsg(err) });\n    return false;\n  }\n  const liveSlugs = new Set(collections.map((collection) => collection.slug));\n  const vanishedMutated = stopVanishedWatchers(gen, liveSlugs);\n  const schemaMutated = await reconcileChangedSchemas(gen, collections);\n  const addedMutated = await startNewWatchers(gen, collections);\n  if (!vanishedMutated && !schemaMutated && !addedMutated) return false;\n  await sweepStaleActiveEntries(gen.discoveryOpts);\n  return true;\n}\n\nfunction stopVanishedWatchers(gen: WatcherGeneration, liveSlugs: Set<string>): boolean {\n  let mutated = false;\n  for (const slug of [...gen.watchers.keys()]) {\n    if (liveSlugs.has(slug)) continue;\n    const watcher = gen.watchers.get(slug);\n    if (watcher) {\n      try {\n        watcher.unsubscribe();\n      } catch {\n        /* best-effort */\n      }\n    }\n    gen.watchers.delete(slug);\n    mutated = true;\n    log().info(\"watcher stopped\", { slug });\n  }\n  return mutated;\n}\n\n/** True when a schema edit moved the collection's storage — a different\n *  `dataSource.path`, a different `dataPath`, or a flip between the two\n *  modes. The mounted fs.watch is bound to the OLD location, so it must\n *  be remounted, not just re-reconciled. */\nfunction storagePathChanged(previous: LoadedCollection[\"schema\"], next: LoadedCollection[\"schema\"]): boolean {\n  return previous.dataSource?.path !== next.dataSource?.path || previous.dataPath !== next.dataPath || previous.storage?.path !== next.storage?.path;\n}\n\n/** Re-reconcile already-watched collections whose schema changed since\n *  the last tick. New collections fall through to `startNewWatchers`. */\nasync function reconcileChangedSchemas(gen: WatcherGeneration, collections: readonly LoadedCollection[]): Promise<boolean> {\n  let mutated = false;\n  for (const collection of collections) {\n    const existing = gen.watchers.get(collection.slug);\n    if (!existing) continue;\n    const nextJson = JSON.stringify(collection.schema);\n    if (existing.schemaJson === nextJson) continue;\n    if (storagePathChanged(existing.collection.schema, collection.schema)) {\n      // Drop the stale mount; `startNewWatchers` (which runs right after\n      // this pass in syncWatchers) remounts on the new location. A\n      // dataSource collection also gets a change ping so open views\n      // refetch against the new file immediately.\n      log().info(\"watcher storage path changed, remounting\", { slug: collection.slug });\n      try {\n        existing.unsubscribe();\n      } catch {\n        /* best-effort */\n      }\n      gen.watchers.delete(collection.slug);\n      if (collection.schema.dataSource !== undefined) safePublish(gen, { slug: collection.slug, op: \"upsert\" });\n      mutated = true;\n      continue;\n    }\n    existing.schemaJson = nextJson;\n    existing.collection = collection;\n    log().info(\"watcher schema changed, re-reconciling\", { slug: collection.slug });\n    // Completion rules live in the SCHEMA, so a schema-only edit can change\n    // which items are pending without any record changing. This runs for\n    // every backend including dataSource: those rows are read-only, but the\n    // rules applied to them are not. Re-deriving no-ops unless the schema\n    // declares `completionField`; a completionField that was REMOVED is the\n    // sweep's job, which `mutated` schedules at the end of the tick.\n    await reconcileAllItems(collection, gen.discoveryOpts);\n    if (collection.schema.dataSource !== undefined) {\n      // Read-only rows can't have changed, but a schema edit changes what the\n      // views render (fields, displayField, …), so ping them.\n      safePublish(gen, { slug: collection.slug, op: \"upsert\" });\n    }\n    mutated = true;\n  }\n  return mutated;\n}\n\n/** Mount a watcher for every collection that doesn't have one yet. Returns\n *  whether the watcher SET actually changed — the starters report whether\n *  they mounted, because ATTEMPTING is not mounting. A start that throws\n *  (logged and swallowed inside the starter) leaves `watchers` untouched, so\n *  the collection is retried next tick; counting that as a mutation made\n *  `syncWatchers` sweep on every tick for as long as the failure persisted. */\nasync function startNewWatchers(gen: WatcherGeneration, collections: readonly LoadedCollection[]): Promise<boolean> {\n  let mutated = false;\n  for (const collection of collections) {\n    if (gen.watchers.has(collection.slug)) continue;\n    if (await startWatcherFor(gen, collection)) mutated = true;\n  }\n  return mutated;\n}\n\n/** Mount one collection's change subscription, whatever its backend.\n *\n *  The store decides how to detect a change and at what granularity; this\n *  decides what to do about one. That split is the point: adding a backend\n *  means implementing `watch` on its store, not another branch here.\n *\n *  A store without `watch` cannot report external changes at all — it is\n *  still registered (so bells reconcile at boot and on the clock tick), just\n *  without live updates. */\nasync function startWatcherFor(gen: WatcherGeneration, collection: LoadedCollection): Promise<boolean> {\n  const { slug } = collection;\n  try {\n    // Boot reconcile BEFORE subscribing: an item that went pending while the\n    // server was down needs its bell even if no event ever fires.\n    await reconcileAllItems(collection, gen.discoveryOpts);\n    const store = storeFor(collection, gen.discoveryOpts);\n    const unsubscribe = store.watch\n      ? await store.watch((change) => {\n          void handleStoreChange(gen, slug, change).catch((err: unknown) => {\n            log().warn(\"store change handling failed\", { slug, error: errMsg(err) });\n          });\n        })\n      : () => {};\n    // `null` means the backend HAS a watch but could not arm it this time.\n    // Registering anyway would mark the slug mounted forever: `startNewWatchers`\n    // skips slugs already in `watchers`, so nothing would re-arm it and the\n    // collection would serve stale data until a restart. Leave it out and the\n    // next sync tick retries — the boot reconcile above is idempotent.\n    if (unsubscribe === null) {\n      log().warn(\"collection watcher could not arm, retrying next sync\", { slug });\n      return false;\n    }\n    gen.watchers.set(slug, { slug, dataDir: collection.dataDir, unsubscribe, schemaJson: JSON.stringify(collection.schema), collection });\n    log().info(\"collection watcher started\", { slug, live: store.watch !== undefined });\n    return true;\n  } catch (err) {\n    log().warn(\"collection watcher start failed\", { slug, error: errMsg(err) });\n    return false;\n  }\n}\n\n/** React to one reported change. Backend-agnostic by construction — it sees\n *  only the granularity the store reported.\n *\n *  `item`: reconcile just that record, then publish it. `collection`: the\n *  store couldn't say which record, so re-derive everything and pair it with\n *  a sweep — a record deleted remotely leaves a bell that a walk over the\n *  SURVIVING records can never clear. */\nasync function handleStoreChange(gen: WatcherGeneration, slug: string, change: StoreChange): Promise<void> {\n  if (change.kind === \"collection\") {\n    await scheduleCollectionReconcile(gen, slug);\n    return;\n  }\n  // Resolve the collection at EVENT time, not at mount time. A schema-only\n  // edit (one that leaves the storage location alone, so nothing remounts)\n  // refreshes `watchers`' entry in place — a callback closed over the mount-\n  // time snapshot would keep reconciling against the old `completionField` /\n  // `notifyWhen` / `triggerField` and undo what the schema-change pass had\n  // just converged on.\n  const current = gen.watchers.get(slug)?.collection;\n  if (!current) return; // unmounted between the event and now\n  await scheduleItemReconcile(gen, current, change.itemId);\n}\n\n/** Full re-derivation for a collection-granularity change, single-flighted\n *  per slug so a burst of writes collapses into one pass plus one trailing\n *  re-run. */\nfunction scheduleCollectionReconcile(gen: WatcherGeneration, slug: string): Promise<void> {\n  return runSingleFlight(\n    gen.collectionSlots,\n    slug,\n    async () => {\n      const collection = await loadCollection(slug, gen.discoveryOpts);\n      if (!collection) return;\n      await reconcileAllItems(collection, gen.discoveryOpts);\n      // A record deleted underneath us leaves a bell that a walk over the\n      // SURVIVING records can never clear — the sweep is the other half.\n      await sweepStaleActiveEntries(gen.discoveryOpts);\n    },\n    // Publish from `onSettled`, i.e. even when the pass above threw: the\n    // data changed regardless of whether we managed to re-derive bells from\n    // it, and a missed event leaves every open view silently stale. The slot\n    // is the coalescing unit, so one burst still yields one publish.\n    () => {\n      safePublish(gen, { slug, op: \"upsert\" });\n      return Promise.resolve();\n    },\n  );\n}\n\n/** Test-only: feed one store-reported change through the same path a live\n *  subscription uses, so a test can pin how a change is reacted to without\n *  depending on fs.watch timing. */\nexport function _handleStoreChangeForTesting(slug: string, change: StoreChange, workspaceRoot?: string): Promise<void> {\n  const gen = resolveGeneration(workspaceRoot);\n  return gen ? handleStoreChange(gen, slug, change) : Promise.resolve();\n}\n\n/** Test-only: drive one collection-granularity reconcile directly. */\nexport function _scheduleCollectionReconcileForTesting(slug: string, workspaceRoot?: string): Promise<void> {\n  const gen = resolveGeneration(workspaceRoot) ?? fallbackGeneration();\n  return scheduleCollectionReconcile(gen, slug);\n}\n\nexport function _scheduleItemReconcileForTesting(collection: LoadedCollection, itemId: string, workspaceRoot?: string): Promise<void> {\n  // Falling back to the detached generation keeps the helpers usable with NO\n  // watcher running at all — several tests drive the reconcile path directly\n  // and never call `startCollectionWatchers`.\n  const gen = resolveGeneration(workspaceRoot) ?? fallbackGeneration();\n  return scheduleItemReconcile(gen, collection, itemId);\n}\n\nfunction scheduleItemReconcile(gen: WatcherGeneration, collection: LoadedCollection, itemId: string): Promise<void> {\n  return runSingleFlight(\n    gen.itemSlots,\n    `${collection.slug}\\x00${itemId}`,\n    () => reconcileItem(collection, itemId, gen.discoveryOpts),\n    () => publishItemChange(gen, collection, itemId),\n  );\n}\n\n/** The shared single-flight loop behind both schedulers. Re-runs `pass`\n *  while events keep arriving — the trailing re-run captures any state\n *  change that landed during a prior pass. After each pass we read\n *  `pending` and zero it before the next iteration, so an event that\n *  fires *during* the last pass's await still triggers one more pass\n *  before the slot is freed. `onSettled`, if given, runs once in the\n *  `finally` after the slot is freed — the item scheduler uses it to\n *  publish exactly one live-refresh event per burst. */\nfunction runSingleFlight(slots: Map<string, ReconcileSlot>, key: string, pass: () => Promise<void>, onSettled?: () => Promise<void>): Promise<void> {\n  const existing = slots.get(key);\n  if (existing) {\n    existing.pending = true;\n    return existing.running;\n  }\n  const slot: ReconcileSlot = { running: Promise.resolve(), pending: false };\n  slot.running = (async () => {\n    try {\n      let keepGoing = true;\n      while (keepGoing) {\n        slot.pending = false;\n        await pass();\n        keepGoing = slot.pending;\n      }\n    } finally {\n      slots.delete(key);\n      // `onSettled` runs once after the slot is freed — the slot is the\n      // coalescing primitive, so one burst yields one call. It's in the\n      // `finally` because a failed reconcile still means a file changed,\n      // and open views must be told to refetch either way.\n      if (onSettled) await onSettled();\n    }\n  })();\n  slots.set(key, slot);\n  return slot.running;\n}\n\n/** Emit the live-refresh event for one record. `op` is derived from\n *  whether the file is still there — `fs.watch` reports neither the kind\n *  of change nor, reliably, which of `rename`/`change` means what. Only\n *  file-backed collections reach here; a `storage` (db) collection has no\n *  per-record file and publishes wholesale in `scheduleStorageReconcile`. */\nasync function publishItemChange(gen: WatcherGeneration, collection: LoadedCollection, itemId: string): Promise<void> {\n  const changeOp = (await itemFileExists(collection.dataDir, itemId)) ? \"upsert\" : \"delete\";\n  safePublish(gen, { slug: collection.slug, ids: [itemId], op: changeOp });\n}\n\nasync function itemFileExists(dataDir: string, itemId: string): Promise<boolean> {\n  try {\n    await access(itemFilePath(dataDir, itemId));\n    return true;\n  } catch {\n    return false;\n  }\n}\n\n/** The publisher is host-supplied, so treat it as untrusted: a throw here\n *  runs inside a `finally` and would mask the reconcile's own error.\n *\n *  Stamps the root the watcher is RUNNING for (`discoveryOpts.workspaceRoot`)\n *  rather than deriving one from `collection.dataDir` — a `LoadedCollection`\n *  carries only absolute paths, and a root reconstructed by string surgery\n *  would be a guess. Undefined in a single-workspace host, which is exactly\n *  what the payload contract means by \"the host's configured root\". */\nfunction safePublish(gen: WatcherGeneration, payload: Omit<CollectionChangePayload, \"root\">): void {\n  const enriched = collectionChangePayload(payload, gen.discoveryOpts.workspaceRoot);\n  try {\n    publishCollectionChange(enriched);\n  } catch (err) {\n    log().warn(\"collection change publish failed\", { slug: enriched.slug, error: errMsg(err) });\n  }\n}\n"],"mappings":"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"}