This repository has no description

dsh-plugins fix-workspace-multiple-writers: add the workspace store reconciler master

Two dsh instances sharing one `$DSH_HOME` each keep the whole workspace store in memory: the JSON backend reads the document once at open, the domain loads its tables once, and the registry holds its own record snapshots. A whole-file publish from one instance therefore silently drops additions the other made, and no process ever notices. This plugin watches `storages/workspace.json` and replays the *additions* it describes through `ctx.workspaceRegistry`'s public API — the same write path a session create uses — so the attach emits `domain/changed` and every connected browser shows the session without a reload. Additive-only by construction: the planner's result type has no removal, reorder, or rename action at all. `updatedAt` cannot arbitrate a conflict (a stale whole-file write is indistinguishable from a deliberate removal), so any destructive reconciliation would be a guess, and the reported bug is lost additions. Decisions worth knowing: - The store path is derived from the open domain's own unit rather than assumed from `$DSH_HOME/storages/workspace.json`. A relocated backend root would otherwise leave the plugin watching a stale file and replaying it into the live registry; a directory-backed (`per-record`) unit makes it decline to run rather than read a directory as a document. - Local membership comes from the durable records, because `Workspace.sessionIds` is cwd-index-filtered: planning an attach from that view makes the registry's prune step write an accounted id out. When the durable accounts are unreadable the planner attaches only to workspaces it is about to create, since a fresh record cannot be pruned. - Passes are debounced onto a single-flight chain, and the store's containing directory is watched rather than the file, because the backend commits by temp-write + `rename()`. A watch that cannot be created degrades to `fs.watchFile`, which polls the path itself. - Retries are bounded. A `pendingMutation` left by a writer that died between its two writes would otherwise poll every 250 ms forever while disabling reconciliation. Limits, documented in the plugin README: it never removes, reorders, or renames; the registry prunes locally-unvalidatable ids on *any* write to that record, including one this plugin induces; and every registry write republishes the whole file from this process's one-time snapshot, so a peer's concurrent addition can be reverted inside the read-to-commit window — and the peer will not repair it, since its plan is store-minus-local and its own addition is still in its memory. Closing that window needs cross-process serialization of the read-modify-write, which is out of scope here. Zero runtime dependencies: a plugin mounted through the loader's `cordis:include` resolves its imports from its own directory, so only `node:` built-ins are imported. `HANDOFF.md` is the design record this implementation follows.


Author Yuto Nishida Committer Tangled Date Commit a21c49a7 Parent adf770af Change ID kpxlrymk
+1285
6 changed files