@openparachute/vault 0.7.6 → 0.7.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +16 -16
  2. package/core/src/attachment/policy.test.ts +7 -0
  3. package/core/src/attachment/policy.ts +9 -0
  4. package/core/src/attachment-tickets-tool.test.ts +15 -0
  5. package/core/src/conformance.test.ts +78 -0
  6. package/core/src/conformance.ts +34 -5
  7. package/core/src/connection-pragmas.test.ts +27 -1
  8. package/core/src/core.test.ts +88 -3
  9. package/core/src/cursor.ts +2 -0
  10. package/core/src/do-param-cap.test.ts +167 -0
  11. package/core/src/lede.test.ts +60 -0
  12. package/core/src/mcp-manifest.ts +15 -1
  13. package/core/src/mcp.ts +37 -5
  14. package/core/src/notes.ts +120 -48
  15. package/core/src/query-operators.ts +87 -5
  16. package/core/src/query-warnings.ts +11 -3
  17. package/core/src/schema.ts +32 -0
  18. package/core/src/seed-packs.ts +74 -5
  19. package/core/src/sql-in.ts +32 -2
  20. package/core/src/store.ts +16 -49
  21. package/core/src/test-preload.ts +48 -3
  22. package/core/src/types.ts +13 -1
  23. package/core/src/wikilinks.test.ts +57 -0
  24. package/core/src/wikilinks.ts +63 -29
  25. package/package.json +1 -1
  26. package/src/attachment-tickets.test.ts +62 -0
  27. package/src/attachment-tickets.ts +2 -2
  28. package/src/cli.ts +36 -8
  29. package/src/config.ts +34 -1
  30. package/src/contract-honest-queries.test.ts +33 -1
  31. package/src/contract-search.test.ts +47 -0
  32. package/src/embedding/select.ts +16 -3
  33. package/src/live-match.test.ts +8 -0
  34. package/src/live-match.ts +15 -0
  35. package/src/mcp-http.test.ts +12 -0
  36. package/src/mcp-http.ts +1 -0
  37. package/src/mcp-tools.ts +51 -17
  38. package/src/mirror-routes.test.ts +22 -31
  39. package/src/onboarding-seed.test.ts +68 -0
  40. package/src/routes.ts +88 -25
  41. package/src/routing.test.ts +24 -0
  42. package/src/routing.ts +2 -0
  43. package/src/subscriptions.ts +18 -2
  44. package/src/tag-scope-note-tags.test.ts +476 -0
  45. package/src/tag-scope.ts +73 -5
  46. package/src/test-home-isolation.test.ts +137 -0
  47. package/src/test-support/spawn.ts +12 -0
  48. package/src/transcription/download.test.ts +187 -1
  49. package/src/transcription/download.ts +149 -2
  50. package/src/transcription/install-python.test.ts +23 -2
  51. package/src/transcription/install-python.ts +13 -4
  52. package/src/vault.test.ts +39 -4
  53. package/src/version.test.ts +8 -0
  54. package/src/ws-server.ts +12 -2
package/src/vault.test.ts CHANGED
@@ -238,6 +238,14 @@ describe("BunStore", async () => {
238
238
  expect(results.map((n) => n.path)).toContain("Projects/Parachute/README");
239
239
  expect(results.map((n) => n.path)).toContain("Projects/Parachute/Notes");
240
240
  });
241
+
242
+ test("queryNotes excludePathPrefix drops matching paths (vault#628)", async () => {
243
+ await store.createNote("user", { path: "Projects/a" });
244
+ await store.createNote("sys", { path: ".parachute/notes/settings" });
245
+ await store.createNote("bare");
246
+ const results = await store.queryNotes({ excludePathPrefix: [".parachute/"] });
247
+ expect(results.map((n) => n.content).sort()).toEqual(["bare", "user"]);
248
+ });
241
249
  });
242
250
 
243
251
  describe("metadata", async () => {
@@ -1877,11 +1885,16 @@ describe("scoped MCP wrapper", async () => {
1877
1885
  // note, so it's not a schema-shape indicator.)
1878
1886
  const serialized = JSON.stringify(result);
1879
1887
  expect(serialized).not.toContain("fizzbuzz");
1880
- // The tag NAME must be gone from validation_status specifically. NOTE:
1881
- // it still appears in the note's `.tags` array — that's PRE-EXISTING
1882
- // scoped-read behavior (noteWithinTagScope has never scrubbed a returned
1883
- // note's tag set), independent of fix 3, and out of scope for this fix.
1888
+ // The tag NAME must be gone from validation_status specifically.
1889
+ // (Historical note: this assertion used to be narrowed to
1890
+ // `validation_status` because the name STILL appeared in the note's
1891
+ // `.tags` array pre-existing scoped-read behavior, filed as vault#568
1892
+ // and out of scope for fix 3. #568 has since scrubbed `.tags` too, so
1893
+ // the name is now absent from the whole response; the narrow assertion
1894
+ // is kept as-is because it pins THIS fix's field specifically.
1895
+ // `tag-scope-note-tags.test.ts` owns the `.tags` coverage.)
1884
1896
  expect(JSON.stringify(result.validation_status)).not.toContain("project-manhattan");
1897
+ expect(result.tags).toEqual(["work"]);
1885
1898
 
1886
1899
  // The in-scope tag's own warning DOES survive.
1887
1900
  const vs = result.validation_status;
@@ -2496,6 +2509,20 @@ describe("HTTP /notes", async () => {
2496
2509
  expect(body.preview).toBe("hello");
2497
2510
  });
2498
2511
 
2512
+ test("GET /notes?exclude_path_prefix=.parachute/ drops system-space (vault#628)", async () => {
2513
+ await store.createNote("user", { path: "Projects/a" });
2514
+ await store.createNote("sys", { path: ".parachute/notes/settings" });
2515
+ await store.createNote("bare");
2516
+ const res = await handleNotes(
2517
+ mkReq("GET", "/notes?exclude_path_prefix=.parachute/&include_content=true"),
2518
+ store,
2519
+ "",
2520
+ );
2521
+ expect(res.status).toBe(200);
2522
+ const body = await res.json() as any[];
2523
+ expect(body.map((n) => n.content).sort()).toEqual(["bare", "user"]);
2524
+ });
2525
+
2499
2526
  test("GET /notes?include_metadata=false strips metadata from list", async () => {
2500
2527
  await store.createNote("a", { tags: ["m"], metadata: { summary: "hello", status: "ok" } });
2501
2528
  await store.createNote("b", { tags: ["m"], metadata: { summary: "world" } });
@@ -2530,6 +2557,14 @@ describe("HTTP /notes", async () => {
2530
2557
  expect(body.metadata).toEqual({ summary: "s" });
2531
2558
  });
2532
2559
 
2560
+ test("GET /notes?include_metadata=nonexistent keeps metadata: {} (vault#600)", async () => {
2561
+ await store.createNote("a", { tags: ["m600"], metadata: { summary: "hello" } });
2562
+ const res = await handleNotes(mkReq("GET", "/notes?tag=m600&include_metadata=nonexistent"), store, "");
2563
+ const body = await res.json() as any[];
2564
+ expect(body).toHaveLength(1);
2565
+ expect(body[0]).toHaveProperty("metadata", {});
2566
+ });
2567
+
2533
2568
  test("GET /notes/:id?expand_links=true inlines wikilink content", async () => {
2534
2569
  await store.createNote("target body", { path: "Target" });
2535
2570
  await store.createNote("see [[Target]]", { id: "src", path: "Src" });
@@ -21,6 +21,14 @@ function runCli(args: string[]): {
21
21
  cmd: ["bun", CLI, ...args],
22
22
  stdout: "pipe",
23
23
  stderr: "pipe",
24
+ // `env` is NOT optional here. Bun hands a child the environ the parent
25
+ // process *started* with, not the live `process.env` — so runtime
26
+ // assignments never reach it, including the temp PARACHUTE_HOME that
27
+ // `core/src/test-preload.ts` sets for the suite. Spawn without this and
28
+ // the child resolves its parachute home to the developer's real
29
+ // `~/.parachute`. Nothing here writes, but the next copy of this helper
30
+ // might; spreading `process.env` is the shape every CLI test should use.
31
+ env: { ...process.env } as Record<string, string>,
24
32
  });
25
33
  return {
26
34
  exitCode: proc.exitCode ?? -1,
package/src/ws-server.ts CHANGED
@@ -37,7 +37,7 @@ import { readVaultConfig } from "./config.ts";
37
37
  import { getVaultStore } from "./vault-store.ts";
38
38
  import { authenticateVaultRequest, type AuthResult } from "./auth.ts";
39
39
  import { hasScopeForVault } from "./scopes.ts";
40
- import { expandTokenTagScope, filterNotesByTagScope } from "./tag-scope.ts";
40
+ import { expandTokenTagScope, filterNotesByTagScope, scrubNotesTagsByScope } from "./tag-scope.ts";
41
41
  import { buildLiveMatcher } from "./live-match.ts";
42
42
  import {
43
43
  subscriptionManager,
@@ -254,7 +254,17 @@ export function createSubscribeWsBinding(deps: SubscribeWsDeps = {}): {
254
254
  // Project AFTER the tag-scope filter (which reads note.tags). `toNoteIndex`
255
255
  // is the SAME lean shape the REST list route returns, so a client that
256
256
  // renders REST lists renders these snapshot frames unchanged.
257
- const frames = buildSnapshotFrames(lean ? snapshotNotes.map(toNoteIndex) : snapshotNotes);
257
+ //
258
+ // Then scrub each surviving note's `.tags` down to the in-scope subset
259
+ // (vault#568) — a snapshot is a read, and it must not disclose the NAMES
260
+ // of a visible note's out-of-scope co-tags any more than `GET /api/notes`
261
+ // does. Scrubbing AFTER the projection keeps the filter reading the full
262
+ // tag set. No-op for an unscoped subscription.
263
+ const frames = buildSnapshotFrames(
264
+ lean
265
+ ? scrubNotesTagsByScope(snapshotNotes.map(toNoteIndex), tagScopeAllowed, auth.scoped_tags)
266
+ : scrubNotesTagsByScope(snapshotNotes, tagScopeAllowed, auth.scoped_tags),
267
+ );
258
268
 
259
269
  // --- SYNCHRONOUS from here (no await) so no write interleaves between
260
270
  // register and the snapshot flush.