The fs seam split left four pieces of pre-split surface populated on
every call and read by nobody:
- STREAM_MIN_SIZE + FsIoInternals.streamMinSize in dsh-fs-local: the
backend has no read routing (readWholeText/streamWholeText are
separate primitives the caller picks), and the real 10 MiB routing
constant lives in dsh-tool-fs's read tool. Delete the dead mirror and
the knob whose JSDoc claimed an override that did not exist; the
remaining FsIoInternals knobs stay (the atomic-write tests use them).
- FsTarget.inputPath: a "diagnostics only" field every backend and test
fake had to fabricate, with zero production readers (policy and error
messages use targetKey/displayPath). listDir gave children the bare
entry name, which was nobody's input.
- FsEditOutcome.replacements/.replaceAll: replacements had no reader
(the single-match policy is enforced by the FS_AMBIGUOUS_EDIT /
FS_EDIT_NOT_FOUND throws, whose message keeps the internal count);
replaceAll only echoed the replace_all argument back to
formatEditOutput, which now takes it from the parsed args. The
outcome shrinks to { version, before, after }, parallel to
FsWriteOutcome's backend-discovered fields. Emitted text is unchanged
for both branches (no snapshot churn).
- FileReadOutcome.limit/.version: formatReadOutput renders
offset/lines/totalLines/truncatedByBytes only, and the fs/observed
emit uses info.version directly.
Backends shed four fabrication obligations and gain none. Doc pastes
(core-data-structures/filesystem.md), the dsh-fs README resolve row,
and the test fakes shrink with the types. RFC moved to
implemented/simplification and amended to the shipped shape
(FsEditSpec -> FsEditRequest name fix; manifest rows needed no change).
@deepseek-ai/dsh-fs-policy
The fs-policy plugin: it adds observed-state, read-before-edit, and version-guarded write/edit on top of the ctx.fs provider seam (@deepseek-ai/dsh-fs) — through the fs/* event gate, NOT through a method service. This plugin registers no ctx.fsPolicy service and has no public read/write/edit/resolve methods. It is the policy third of the filesystem stack: not a swappable seam, but the policy that does not belong on the FileSystem provider base class.
import type { Context } from 'cordis'
import * as FsPolicy from '@deepseek-ai/dsh-fs-policy'
declare const ctx: Context
// No service to inject — this plugin only registers the three fs/* listeners.
// Load it alongside a ctx.fs provider (e.g. @deepseek-ai/dsh-fs-local) and the
// @deepseek-ai/dsh-tool-fs tools; the tools dispatch the fs/* events this plugin
// decides. Order does not matter for resolution (no inject), but the policy
// listener should be the first decider registered for the fs/*-intent slots.
await ctx.plugin(FsPolicy)
The four-layer split
| Layer | Package | Role |
|---|---|---|
| tool / executor | @deepseek-ai/dsh-tool-fs |
model-facing schemas + read windowing + text rendering; reads/writes/edits via ctx.fs, dispatches the fs/* events |
| policy | @deepseek-ai/dsh-fs-policy (this) |
observed-state + read-before-edit + version-guarded write/edit, contributed through the fs/* event gate (no service) |
| provider seam | @deepseek-ai/dsh-fs |
ctx.fs: text IO + atomic mutation primitives (optional version guard); owns the fs/* event vocabulary |
| provider | @deepseek-ai/dsh-fs-local |
local implementation of ctx.fs |
How the gate participates
Three fs/* events (declared by @deepseek-ai/dsh-fs, dispatched by @deepseek-ai/dsh-tool-fs):
| Event | This plugin's listener |
|---|---|
fs/write-intent |
No prior observation → { kind: 'createIfAbsent' }; a prior observation → { kind: 'replaceIfVersion', version: vObserved }. Single-slot decision; does NOT call next(). |
fs/edit-intent |
Requires a prior observation by this owner (else throws FS_NOT_OBSERVED); returns { version: vObserved } as the CAS basis. Single-slot decision; does NOT call next(). |
fs/observed |
Records { version } for this owner+target. Synchronous, side-effect-only WeakMap.set. |
Observed state is the prior-observation record; freshness is provider CAS
Observed state is a WeakMap<owner, Map<targetKey, FsVersion>>. An entry exists iff the owner has read, written, OR edited that target (every success emits fs/observed), so its presence is the prior-observation record — there is no hasRead flag and no full/partial view. This plugin does no filesystem I/O: "have you observed this file?" is a WeakMap lookup, and "is the version you read still current?" is decided inside ctx.fs.editText/writeText in the same atomic lock that performs the mutation — this plugin only supplies vObserved as the basis. A windowed read of lines 100-150 records the file's version, and a later edit of line 120 is authorized as long as the file is unchanged. State is held weakly and dropped on disposal (HMR safety); persistence across sessions is deferred.
Single-slot, first-wins
The fs/write-intent/fs/edit-intent slots hold exactly one decider — this plugin fully decides and does not call next(). The slot is first-wins by registration order; this plugin owning it is the default-deployment convention, not an event-enforced invariant (a decider registered before / prepended would win instead). This is not a composable authorization chain — layered permission/audit/sandbox interception belongs on tools/execute.
No method coupling
Because the plugin influences the world only through events, removing it does not break @deepseek-ai/dsh-tool-fs at a service-injection boundary: the tool falls through to the bare ctx.fs provider (unconditional write/edit, no observed-state). Loading it back layers the policy on. That graceful add/remove is the whole point of the event gate over a mandatory method service.