Commit graph

2 commits

Author SHA1 Message Date
Aaron Bockelie
a08347f4be fix(dispatch): address PR #212 review findings
- vault.update content-guard hint pointed at vault.patch, which does not
  exist — patch lives at edit.patch. Bad recovery hint is worse than no
  hint.
- edit.window guard test claimed "before searching the file" but only
  asserted on mutations; the mock's 'original' content doesn't contain
  'undefined' so even a leaked guard wouldn't show a write. Add a reads
  recorder and assert getFile is never called, making the test honest
  about what it proves.
- Strip a stray space from the lock-key guard comment.
2026-05-25 08:31:37 -05:00
Aaron Bockelie
000f69551a fix(dispatch): require params at the MCP boundary (closes #210)
#210 reported that `vault.update` with no `content` writes the literal
9-byte string `"undefined"` to the target file — `String(undefined)`
coerced silently, the validator accepted the 9-byte string, and the
formatter rendered the misleading heading `✓ Updated: undefined`.

The same `String(params.x)` footgun existed at every vault-mutating
dispatch site: `edit.append` (would append "undefined"), `edit.window`
(would search for or replace with "undefined"), plus path-only cases
that produced cryptic "File not found: undefined" errors instead of a
clean dispatch-level rejection.

Fix the class, not just the instance:

- Add `requireParamStr(params, key, action, hint?)` in operations/shared.
  Throws at the boundary when a required string param is missing or
  wrong-typed, so a malformed MCP call cannot reach a vault sink.
- Convert the corruption-class sites (`vault.update`, `vault.create`,
  `vault.delete`, `edit.append`, `edit.window`) and the noisy path-only
  sites (`edit.patch`, `edit.at_line`, `edit.from_buffer`, `view.file`,
  `view.window`, `view.open_in_obsidian`) to use the helper.
- `vault.update`'s `content` guard carries a hint pointing at
  `vault.patch` / `edit.window` — that's the wrong-tool path that
  produced the #210 corruption in the wild.
- Lift `edit.*`'s path guard above `FileLockManager.withLock` so a
  missing path can't take a lock on the literal string "undefined".
- `updateFile` / `deleteFile` now return `{ success, path }` so
  `formatFileWrite` renders `Updated: <path>` — belt-and-suspenders for
  the second issue called out in #210.

Tests: `tests/dispatch-param-guards.test.ts` records every mutation a
mock API receives; for each malformed call we assert the route rejects
AND no mutation was recorded, proving the guard runs before any sink.
2026-05-25 07:54:04 -05:00