@openparachute/vault 0.6.4 → 0.6.5-rc.10

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 (53) hide show
  1. package/core/src/__fixtures__/golden-vault.ts +125 -0
  2. package/core/src/__fixtures__/portable-export-golden.json +10 -0
  3. package/core/src/do-param-cap.test.ts +161 -0
  4. package/core/src/links.ts +8 -13
  5. package/core/src/mcp.ts +306 -314
  6. package/core/src/notes.ts +54 -62
  7. package/core/src/onboarding.ts +14 -296
  8. package/core/src/portable-md-batching.test.ts +67 -0
  9. package/core/src/portable-md-golden.test.ts +55 -0
  10. package/core/src/portable-md.ts +579 -435
  11. package/core/src/schema.ts +21 -43
  12. package/core/src/seed-packs.test.ts +191 -0
  13. package/core/src/seed-packs.ts +559 -0
  14. package/core/src/sql-in.test.ts +58 -0
  15. package/core/src/sql-in.ts +76 -0
  16. package/core/src/store.ts +14 -9
  17. package/core/src/transcription/provider.ts +141 -0
  18. package/core/src/txn.test.ts +229 -0
  19. package/core/src/txn.ts +105 -0
  20. package/core/src/types.ts +9 -0
  21. package/core/src/vault-projection.ts +1 -1
  22. package/core/src/wikilinks.ts +10 -4
  23. package/package.json +1 -1
  24. package/src/add-pack.test.ts +142 -0
  25. package/src/cli.ts +778 -7
  26. package/src/onboarding-seed.test.ts +126 -40
  27. package/src/onboarding-seed.ts +41 -46
  28. package/src/routes.ts +25 -7
  29. package/src/server.ts +108 -31
  30. package/src/transcription/build.test.ts +224 -0
  31. package/src/transcription/build.ts +252 -0
  32. package/src/transcription/capability.test.ts +118 -0
  33. package/src/transcription/capability.ts +96 -0
  34. package/src/transcription/install-python.test.ts +366 -0
  35. package/src/transcription/install-python.ts +471 -0
  36. package/src/transcription/install.test.ts +167 -0
  37. package/src/transcription/install.ts +296 -0
  38. package/src/transcription/providers/onnx-asr.test.ts +229 -0
  39. package/src/transcription/providers/onnx-asr.ts +239 -0
  40. package/src/transcription/providers/parakeet-mlx.test.ts +290 -0
  41. package/src/transcription/providers/parakeet-mlx.ts +242 -0
  42. package/src/transcription/providers/scribe-http.test.ts +195 -0
  43. package/src/transcription/providers/scribe-http.ts +144 -0
  44. package/src/transcription/providers/transcribe-cpp.test.ts +314 -0
  45. package/src/transcription/providers/transcribe-cpp.ts +293 -0
  46. package/src/transcription/select.test.ts +259 -0
  47. package/src/transcription/select.ts +334 -0
  48. package/src/transcription/tiers.test.ts +197 -0
  49. package/src/transcription/tiers.ts +184 -0
  50. package/src/transcription-worker.test.ts +44 -0
  51. package/src/transcription-worker.ts +57 -122
  52. package/src/vault-create.test.ts +38 -10
  53. package/src/vault.test.ts +48 -0
@@ -1,6 +1,7 @@
1
1
  import { Database } from "bun:sqlite";
2
2
  import { normalizePath } from "./paths.js";
3
3
  import { rebuildIndexes } from "./indexed-fields.js";
4
+ import { transaction } from "./txn.js";
4
5
 
5
6
  export const SCHEMA_VERSION = 23;
6
7
 
@@ -680,8 +681,7 @@ function migrateToV13(db: Database): void {
680
681
  function migrateToV14(db: Database): void {
681
682
  if (!hasTable(db, "tags")) return;
682
683
 
683
- db.exec("BEGIN IMMEDIATE");
684
- try {
684
+ const { copiedSchemas, copiedHierarchy } = transaction(db, () => {
685
685
  // 1. ALTER TABLE — additive, idempotent.
686
686
  const cols: [string, string][] = [
687
687
  ["description", "TEXT"],
@@ -762,16 +762,13 @@ function migrateToV14(db: Database): void {
762
762
  db.exec("DROP TABLE tag_schemas");
763
763
  }
764
764
 
765
- db.exec("COMMIT");
765
+ return { copiedSchemas, copiedHierarchy };
766
+ });
766
767
 
767
- if (copiedSchemas > 0 || copiedHierarchy > 0) {
768
- console.log(
769
- `[vault] migrated to schema v14: copied ${copiedSchemas} tag_schemas + ${copiedHierarchy} _tags/* hierarchies onto tags rows`,
770
- );
771
- }
772
- } catch (err) {
773
- db.exec("ROLLBACK");
774
- throw err;
768
+ if (copiedSchemas > 0 || copiedHierarchy > 0) {
769
+ console.log(
770
+ `[vault] migrated to schema v14: copied ${copiedSchemas} tag_schemas + ${copiedHierarchy} _tags/* hierarchies onto tags rows`,
771
+ );
775
772
  }
776
773
  }
777
774
 
@@ -806,8 +803,7 @@ function migrateToV15(db: Database): void {
806
803
  ).get()) !== null;
807
804
  if (hasSchemas || hasMappings) return;
808
805
 
809
- db.exec("BEGIN IMMEDIATE");
810
- try {
806
+ const { copiedSchemas, copiedMappings } = transaction(db, () => {
811
807
  const now = new Date().toISOString();
812
808
  let copiedSchemas = 0;
813
809
  let copiedMappings = 0;
@@ -884,16 +880,13 @@ function migrateToV15(db: Database): void {
884
880
  }
885
881
  }
886
882
 
887
- db.exec("COMMIT");
883
+ return { copiedSchemas, copiedMappings };
884
+ });
888
885
 
889
- if (copiedSchemas > 0 || copiedMappings > 0) {
890
- console.log(
891
- `[vault] migrated to schema v15: copied ${copiedSchemas} _schemas/* + ${copiedMappings} _schema_defaults mappings into note_schemas/schema_mappings`,
892
- );
893
- }
894
- } catch (err) {
895
- db.exec("ROLLBACK");
896
- throw err;
886
+ if (copiedSchemas > 0 || copiedMappings > 0) {
887
+ console.log(
888
+ `[vault] migrated to schema v15: copied ${copiedSchemas} _schemas/* + ${copiedMappings} _schema_defaults mappings into note_schemas/schema_mappings`,
889
+ );
897
890
  }
898
891
  }
899
892
 
@@ -922,15 +915,10 @@ function migrateToV16(db: Database): void {
922
915
  // ALTER block so a fresh vault — where the column exists but the index
923
916
  // doesn't — still gets it.
924
917
  if (!hasColumn(db, "tokens", "vault_name")) {
925
- db.exec("BEGIN IMMEDIATE");
926
- try {
918
+ transaction(db, () => {
927
919
  db.exec("ALTER TABLE tokens ADD COLUMN vault_name TEXT");
928
920
  db.exec("CREATE INDEX IF NOT EXISTS idx_tokens_vault_name ON tokens(vault_name)");
929
- db.exec("COMMIT");
930
- } catch (err) {
931
- db.exec("ROLLBACK");
932
- throw err;
933
- }
921
+ });
934
922
  return;
935
923
  }
936
924
 
@@ -980,8 +968,7 @@ function migrateToV17(db: Database): void {
980
968
  ).all() as { schema_name: string; match_kind: string; match_value: string }[];
981
969
  }
982
970
 
983
- db.exec("BEGIN IMMEDIATE");
984
- try {
971
+ transaction(db, () => {
985
972
  // Drop the index first — the index references the table; SQLite would
986
973
  // tear it down on DROP TABLE but the explicit DROP keeps the order
987
974
  // obvious if a future migration reads from sqlite_master mid-flight.
@@ -993,11 +980,7 @@ function migrateToV17(db: Database): void {
993
980
  if (hasNoteSchemas) {
994
981
  db.exec("DROP TABLE note_schemas");
995
982
  }
996
- db.exec("COMMIT");
997
- } catch (err) {
998
- db.exec("ROLLBACK");
999
- throw err;
1000
- }
983
+ });
1001
984
 
1002
985
  if (droppedSchemas.length > 0 || droppedMappings.length > 0) {
1003
986
  const schemaNames = droppedSchemas.map((s) => s.name).join(", ");
@@ -1052,8 +1035,7 @@ function migrateToV18(db: Database): void {
1052
1035
  const hasNewUnique = indexes.some((r) => r.name === "idx_notes_path_ext_unique");
1053
1036
  if (!needsColumn && hasNewUnique && !hasOldUnique) return;
1054
1037
 
1055
- db.exec("BEGIN IMMEDIATE");
1056
- try {
1038
+ transaction(db, () => {
1057
1039
  if (needsColumn) {
1058
1040
  db.exec("ALTER TABLE notes ADD COLUMN extension TEXT NOT NULL DEFAULT 'md'");
1059
1041
  }
@@ -1065,11 +1047,7 @@ function migrateToV18(db: Database): void {
1065
1047
  "CREATE UNIQUE INDEX idx_notes_path_ext_unique ON notes(path, extension) WHERE path IS NOT NULL",
1066
1048
  );
1067
1049
  }
1068
- db.exec("COMMIT");
1069
- } catch (err) {
1070
- db.exec("ROLLBACK");
1071
- throw err;
1072
- }
1050
+ });
1073
1051
  }
1074
1052
 
1075
1053
  /**
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Content + registry pins for the named seed packs (core/src/seed-packs.ts).
3
+ *
4
+ * Pure content tests — no DB. The applier's behavior (idempotence, seeding)
5
+ * is exercised store-level in src/onboarding-seed.test.ts and CLI-level in
6
+ * src/add-pack.test.ts.
7
+ *
8
+ * The load-bearing pin: the `welcome` pack's tags must stay byte-equal to
9
+ * notes-ui's NOTES_REQUIRED_SCHEMA — the Notes PWA's connect-time audit
10
+ * compares `description` verbatim, and its schema banner only clears when the
11
+ * tags genuinely carry the semantics Notes declares. ONE tag since 2026-07-03:
12
+ * `#capture` only — entry method (text|voice) is note `metadata.source`
13
+ * provenance, not taxonomy (sibling notes-ui PR carries the client side).
14
+ */
15
+
16
+ import { describe, test, expect } from "bun:test";
17
+ import {
18
+ applySeedPack,
19
+ CONNECT_AI_PATH,
20
+ GETTING_STARTED_CONTENT,
21
+ GETTING_STARTED_PACK,
22
+ GETTING_STARTED_PATH,
23
+ getSeedPack,
24
+ listSeedPacks,
25
+ NOTES_REQUIRED_TAGS,
26
+ SEED_PACK_NAMES,
27
+ SURFACE_STARTER_CONTENT,
28
+ SURFACE_STARTER_PACK,
29
+ SURFACE_STARTER_PATH,
30
+ TRY_LINKING_PATH,
31
+ WELCOME_PATH,
32
+ welcomePack,
33
+ } from "./seed-packs.ts";
34
+ import * as onboardingShim from "./onboarding.ts";
35
+
36
+ /**
37
+ * LITERAL copy of `NOTES_REQUIRED_SCHEMA.tags` from
38
+ * parachute-surface/packages/notes-ui/src/lib/vault/schema.ts — deliberately
39
+ * NOT imported (different repo; and the whole point is an independent copy
40
+ * that breaks this test when either side drifts). If this test fails, change
41
+ * notes-ui and the pack in lockstep, never one side alone.
42
+ */
43
+ const NOTES_UI_REQUIRED_SCHEMA_TAGS = [
44
+ {
45
+ name: "capture",
46
+ description: "Notes captured directly by the user (text or voice).",
47
+ },
48
+ ];
49
+
50
+ describe("welcome pack — notes-ui schema parity", () => {
51
+ test("welcome tags are byte-equal to notes-ui's NOTES_REQUIRED_SCHEMA", () => {
52
+ expect(NOTES_REQUIRED_TAGS).toEqual(NOTES_UI_REQUIRED_SCHEMA_TAGS);
53
+ expect(welcomePack().tags).toEqual(NOTES_UI_REQUIRED_SCHEMA_TAGS);
54
+ // Field-level byte pins (toEqual ignores key order; these don't).
55
+ for (const [i, expected] of NOTES_UI_REQUIRED_SCHEMA_TAGS.entries()) {
56
+ expect(NOTES_REQUIRED_TAGS[i]!.name).toBe(expected.name);
57
+ expect(NOTES_REQUIRED_TAGS[i]!.description).toBe(expected.description);
58
+ }
59
+ });
60
+
61
+ test("exactly ONE capture tag — no subtype tags, no parent_names (2026-07-03)", () => {
62
+ // Entry method is metadata.source (text|voice), not taxonomy. The old
63
+ // capture/text + capture/voice subtypes must not creep back into the seed
64
+ // (existing vaults carrying them stay valid — tags are user data).
65
+ expect(NOTES_REQUIRED_TAGS).toHaveLength(1);
66
+ expect(NOTES_REQUIRED_TAGS[0]!.name).toBe("capture");
67
+ expect(NOTES_REQUIRED_TAGS[0]!.parent_names).toBeUndefined();
68
+ });
69
+ });
70
+
71
+ describe("welcome pack — person-voiced welcome web", () => {
72
+ test("three notes forming a linked web (welcome ⇄ try-linking, connect-AI → welcome)", () => {
73
+ const pack = welcomePack();
74
+ expect(pack.name).toBe("welcome");
75
+ expect(pack.notes.map((n) => n.path)).toEqual([
76
+ WELCOME_PATH,
77
+ TRY_LINKING_PATH,
78
+ CONNECT_AI_PATH,
79
+ ]);
80
+
81
+ const [welcome, tryLinking, connectAi] = pack.notes;
82
+ expect(welcome!.content).toContain(`[[${TRY_LINKING_PATH}]]`);
83
+ expect(tryLinking!.content).toContain(`[[${WELCOME_PATH}]]`);
84
+ expect(connectAi!.content).toContain(`[[${WELCOME_PATH}]]`);
85
+ });
86
+
87
+ test("content with consoleOrigin is byte-equal to the cloud's welcome copy", () => {
88
+ // Pinned against parachute-cloud workers/vault/src/welcome.ts
89
+ // `welcomeNotes(consoleOrigin)` — the sibling cloud PR swaps that module
90
+ // for this pack, so the ported content must not drift.
91
+ const origin = "https://cloud.parachute.computer";
92
+ const pack = welcomePack({ consoleOrigin: origin });
93
+
94
+ expect(pack.notes[0]!.content).toBe(`# ${WELCOME_PATH}
95
+
96
+ This vault is yours.
97
+ Write anything.
98
+ Notes can link to each other, like this: [[${TRY_LINKING_PATH}]].
99
+ `);
100
+ expect(pack.notes[1]!.content).toBe(`# ${TRY_LINKING_PATH}
101
+
102
+ Wrap a note's name in double square brackets to make a wikilink, like this one back to [[${WELCOME_PATH}]].
103
+ `);
104
+ expect(pack.notes[2]!.content).toBe(`# ${CONNECT_AI_PATH}
105
+
106
+ Your vault speaks MCP. Grab the connection URL from your console at ${origin}.
107
+ Start from [[${WELCOME_PATH}]].
108
+ `);
109
+ });
110
+
111
+ test("without consoleOrigin the Connect-AI line stays generic (no baked origin)", () => {
112
+ const connectAi = welcomePack().notes[2]!;
113
+ expect(connectAi.content).toContain(
114
+ "Your vault speaks MCP. Grab the connection URL from your console.",
115
+ );
116
+ expect(connectAi.content).not.toContain("undefined");
117
+ expect(connectAi.content).not.toContain("at .");
118
+ });
119
+ });
120
+
121
+ describe("getting-started pack", () => {
122
+ test("carries the doctrine note at the canonical path", () => {
123
+ expect(GETTING_STARTED_PACK.name).toBe("getting-started");
124
+ expect(GETTING_STARTED_PACK.tags).toEqual([]);
125
+ expect(GETTING_STARTED_PACK.notes).toEqual([
126
+ { path: GETTING_STARTED_PATH, content: GETTING_STARTED_CONTENT },
127
+ ]);
128
+ });
129
+
130
+ test("no dangling [[Surface Starter]] wikilink — points at the add-pack flow instead", () => {
131
+ // Surface Starter is out of the default seed (ratified 2026-07-02), so the
132
+ // default-seeded guide must not carry a wikilink to a note that doesn't
133
+ // exist. It names the pack + the way to add it.
134
+ expect(GETTING_STARTED_CONTENT).not.toContain("[[Surface Starter]]");
135
+ expect(GETTING_STARTED_CONTENT).toContain("Surface Starter");
136
+ expect(GETTING_STARTED_CONTENT).toContain("add-pack surface-starter");
137
+ // The surface packages are still named for discoverability.
138
+ expect(GETTING_STARTED_CONTENT).toContain("@openparachute/surface-client");
139
+ expect(GETTING_STARTED_CONTENT).toContain("@openparachute/surface-render");
140
+ });
141
+ });
142
+
143
+ describe("surface-starter pack", () => {
144
+ test("carries the surface guide at the canonical path (opt-in, not default)", () => {
145
+ expect(SURFACE_STARTER_PACK.name).toBe("surface-starter");
146
+ expect(SURFACE_STARTER_PACK.tags).toEqual([]);
147
+ expect(SURFACE_STARTER_PACK.notes).toEqual([
148
+ { path: SURFACE_STARTER_PATH, content: SURFACE_STARTER_CONTENT },
149
+ ]);
150
+ expect(SURFACE_STARTER_PACK.description).toContain("not seeded by default");
151
+ });
152
+
153
+ test("links back to Getting Started (default-seeded, so the wikilink resolves)", () => {
154
+ expect(SURFACE_STARTER_CONTENT).toContain("[[Getting Started]]");
155
+ });
156
+ });
157
+
158
+ describe("pack registry", () => {
159
+ test("lists exactly the three packs, in order", () => {
160
+ expect([...SEED_PACK_NAMES]).toEqual([
161
+ "welcome",
162
+ "getting-started",
163
+ "surface-starter",
164
+ ]);
165
+ expect(listSeedPacks().map((p) => p.name)).toEqual([...SEED_PACK_NAMES]);
166
+ for (const p of listSeedPacks()) {
167
+ expect(p.description.length).toBeGreaterThan(0);
168
+ }
169
+ });
170
+
171
+ test("getSeedPack resolves each name and returns null for unknown", () => {
172
+ for (const name of SEED_PACK_NAMES) {
173
+ expect(getSeedPack(name)?.name).toBe(name);
174
+ }
175
+ expect(getSeedPack("nope")).toBeNull();
176
+ expect(getSeedPack("")).toBeNull();
177
+ });
178
+
179
+ test("applySeedPack is exported for both runtimes", () => {
180
+ expect(typeof applySeedPack).toBe("function");
181
+ });
182
+ });
183
+
184
+ describe("onboarding.ts back-compat shim", () => {
185
+ test("re-exports the canonical paths + contents from seed-packs", () => {
186
+ expect(onboardingShim.GETTING_STARTED_PATH).toBe(GETTING_STARTED_PATH);
187
+ expect(onboardingShim.GETTING_STARTED_CONTENT).toBe(GETTING_STARTED_CONTENT);
188
+ expect(onboardingShim.SURFACE_STARTER_PATH).toBe(SURFACE_STARTER_PATH);
189
+ expect(onboardingShim.SURFACE_STARTER_CONTENT).toBe(SURFACE_STARTER_CONTENT);
190
+ });
191
+ });