@openparachute/vault 0.4.8-rc.9 → 0.4.9-rc.2

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openparachute/vault",
3
- "version": "0.4.8-rc.9",
3
+ "version": "0.4.9-rc.2",
4
4
  "description": "Agent-native knowledge graph. Notes, tags, links over MCP.",
5
5
  "module": "src/cli.ts",
6
6
  "type": "module",
@@ -69,11 +69,16 @@ describe("shouldAutoTranscribe", () => {
69
69
  })).toBe(false);
70
70
  });
71
71
 
72
- test("skips when enabled is unset (no auto_transcribe block in config)", () => {
72
+ test("fires when enabled is unset unset config means ON", () => {
73
+ // Default behavior (no `auto_transcribe` block in config) is opt-out:
74
+ // once an operator has scribe reachable, audio attachments transcribe
75
+ // automatically. Operators wanting it OFF set
76
+ // `auto_transcribe.enabled: false` explicitly. Previously default-off;
77
+ // flipped to default-on so installing scribe is the only opt-in signal.
73
78
  expect(shouldAutoTranscribe("audio/wav", {
74
79
  readGlobalConfigImpl: readGlobalConfig(undefined),
75
80
  getCachedScribeUrlImpl: scribePresent,
76
- })).toBe(false);
81
+ })).toBe(true);
77
82
  });
78
83
 
79
84
  test("skips when scribe URL is undefined (no services.json entry, no env)", () => {
@@ -19,7 +19,11 @@ import { getCachedScribeUrl } from "./scribe-discovery.ts";
19
19
  *
20
20
  * Returns `true` only when ALL three conditions hold:
21
21
  * 1. mime-type starts with `audio/` (case-insensitive).
22
- * 2. `globalConfig.auto_transcribe?.enabled === true`.
22
+ * 2. `globalConfig.auto_transcribe?.enabled` is not explicitly false.
23
+ * Default behavior (when unset) is ON — once an operator has scribe
24
+ * reachable, audio attachments transcribe automatically without a
25
+ * separate config step. Operators who want it OFF set
26
+ * `auto_transcribe.enabled: false` explicitly.
23
27
  * 3. Scribe is discoverable (services.json entry OR SCRIBE_URL env).
24
28
  *
25
29
  * The three conditions are independent guards: a single `false` is sufficient
@@ -40,7 +44,7 @@ export function shouldAutoTranscribe(
40
44
  }
41
45
  const enabled = opts.enabledOverride
42
46
  ?? (opts.readGlobalConfigImpl ?? readGlobalConfig)().auto_transcribe?.enabled
43
- ?? false;
47
+ ?? true;
44
48
  if (!enabled) return false;
45
49
  const url = (opts.getCachedScribeUrlImpl ?? getCachedScribeUrl)();
46
50
  if (!url || !url.trim()) return false;
@@ -19,8 +19,11 @@
19
19
  * - port: GlobalConfig.port, exposed read-only.
20
20
  * - autoTranscribe.*: vault↔scribe handoff (vault#353, design 2026-05-21
21
21
  * Part 2). Three nested fields per design Q4:
22
- * - enabled: boolean toggle, default false (persisted in
23
- * GlobalConfig.auto_transcribe.enabled).
22
+ * - enabled: boolean toggle, default true when scribe is
23
+ * reachable (persisted in
24
+ * GlobalConfig.auto_transcribe.enabled). Default
25
+ * flipped from off → on so installing scribe is
26
+ * the only opt-in signal needed.
24
27
  * - scribeUrl: readOnly — resolved per-process from
25
28
  * `~/.parachute/services.json` via
26
29
  * `scribe-discovery.ts`. Operators can't point at an
@@ -69,10 +72,10 @@ export function buildConfigSchema(): ModuleConfigSchema {
69
72
  properties: {
70
73
  enabled: {
71
74
  type: "boolean",
72
- default: false,
75
+ default: true,
73
76
  title: "Enable auto-transcription",
74
77
  description:
75
- "Master toggle. When false, audio uploads land normally without any scribe interaction. Global — persisted in `GlobalConfig.auto_transcribe.enabled` and applies to every vault on this server. Per-vault control is a future enhancement when multi-vault deployments need it.",
78
+ "Master toggle. Default on audio uploads transcribe automatically when scribe is reachable. Set to false to disable. Global — persisted in `GlobalConfig.auto_transcribe.enabled` and applies to every vault on this server. Per-vault control is a future enhancement when multi-vault deployments need it.",
76
79
  },
77
80
  scribeUrl: {
78
81
  type: "string",
@@ -139,7 +142,10 @@ export function buildConfigValues(
139
142
  return {
140
143
  audio_retention: vaultConfig.audio_retention ?? "keep",
141
144
  autoTranscribe: {
142
- enabled: globalConfig.auto_transcribe?.enabled ?? false,
145
+ // Match shouldAutoTranscribe's `?? true` so the admin SPA displays
146
+ // the same value runtime uses. An unset config row shows `true`
147
+ // because that's what vault will actually do on the next audio upload.
148
+ enabled: globalConfig.auto_transcribe?.enabled ?? true,
143
149
  scribeUrl,
144
150
  },
145
151
  // Legacy alias mirrors `autoTranscribe.scribeUrl` so hubs reading the
@@ -17,7 +17,7 @@
17
17
  * never touch ~/.parachute.
18
18
  */
19
19
 
20
- import { describe, test, expect, beforeEach, afterEach, afterAll } from "bun:test";
20
+ import { describe, test, expect, beforeAll, beforeEach, afterEach, afterAll } from "bun:test";
21
21
  import { rmSync, existsSync, mkdirSync, writeFileSync } from "fs";
22
22
  import { join } from "path";
23
23
  import { tmpdir } from "os";
@@ -606,6 +606,24 @@ describe("MCP 401 WWW-Authenticate challenge (RFC 9728)", () => {
606
606
 
607
607
  const HUB_ORIGIN = "http://127.0.0.1:1939";
608
608
 
609
+ // Process-env isolation: sibling test files (tokens-routes.test.ts,
610
+ // auth-hub-jwt.test.ts) set PARACHUTE_HUB_ORIGIN in their own beforeAll
611
+ // hooks. Bun's test runner shares a single process across test files,
612
+ // and when file-ordering puts those before this one, their hook-set
613
+ // value can still be live when our tests run. Restore the default
614
+ // (unset) here so we test against `DEFAULT_HUB_LOOPBACK`. Caught when
615
+ // vault rc.1 release CI failed with "Received: http://127.0.0.1:34295"
616
+ // — a leaked ephemeral port from another test's fixture.
617
+ let _prevHubOriginRouting: string | undefined;
618
+ beforeAll(() => {
619
+ _prevHubOriginRouting = process.env.PARACHUTE_HUB_ORIGIN;
620
+ delete process.env.PARACHUTE_HUB_ORIGIN;
621
+ });
622
+ afterAll(() => {
623
+ if (_prevHubOriginRouting === undefined) delete process.env.PARACHUTE_HUB_ORIGIN;
624
+ else process.env.PARACHUTE_HUB_ORIGIN = _prevHubOriginRouting;
625
+ });
626
+
609
627
  describe("per-vault OAuth discovery (hub-rooted after workstream E)", () => {
610
628
  test("AS metadata names the hub as issuer + endpoints", async () => {
611
629
  createVault("journal");
@@ -117,6 +117,39 @@ describe("self-register", () => {
117
117
  });
118
118
  });
119
119
 
120
+ test("health path follows the primary vault name (closes vault#369)", () => {
121
+ // Regression: pre-fix self-register used `manifest.health` verbatim
122
+ // (which is `/vault/default/health`) regardless of the actual vault
123
+ // name. A hub-bundled wizard that lets the operator name their vault
124
+ // `notes` produced a services.json entry with `paths: ["/vault/notes"]`
125
+ // but `health: "/vault/default/health"`, so hub's per-module health
126
+ // probe hit a 404 even on a healthy vault. The fix derives health from
127
+ // paths[0]. Caught in the wild on a Render rebuild walkthrough.
128
+ withParachuteHome((home) => {
129
+ const { log, warn } = captureLogs();
130
+ selfRegister({
131
+ version: "0.4.8-rc.3",
132
+ log,
133
+ warn,
134
+ readManifest: () => TEST_MANIFEST,
135
+ resolvePackageRoot: () => "/fake/install/dir",
136
+ // Vault named something other than "default" — the manifest fallback
137
+ // path `/vault/default` should NOT leak into the health URL.
138
+ listVaults: () => ["notes"],
139
+ readGlobalConfig: () => ({ port: 1940, default_vault: "notes" }),
140
+ });
141
+ const parsed = JSON.parse(readFileSync(join(home, "services.json"), "utf8")) as {
142
+ services: { paths: string[]; health: string }[];
143
+ };
144
+ const row = parsed.services[0];
145
+ expect(row.paths).toEqual(["/vault/notes"]);
146
+ expect(row.health).toBe("/vault/notes/health");
147
+ // Sanity: health should never be the literal manifest template
148
+ // when a real vault exists with a different name.
149
+ expect(row.health).not.toBe("/vault/default/health");
150
+ });
151
+ });
152
+
120
153
  test("idempotent — re-running reports no changes", () => {
121
154
  withParachuteHome(() => {
122
155
  const { log, warn } = captureLogs();
@@ -163,6 +163,19 @@ export function selfRegister(deps: SelfRegisterDeps): SelfRegisterResult {
163
163
  const paths = buildVaultServicePaths(globalConfig.default_vault, vaults, manifest.paths);
164
164
  const port = globalConfig.port ?? DEFAULT_PORT;
165
165
 
166
+ // Derive the health path from the primary path (paths[0]) rather than
167
+ // using `manifest.health` verbatim. The manifest's `health` is
168
+ // `/vault/default/health` — a template literal where `default` is the
169
+ // placeholder vault name. When the operator named their vault something
170
+ // other than `default` (e.g. `notes`, `journal`, or — caught in the wild
171
+ // on a Render rebuild — just `vault`), the manifest's `default` doesn't
172
+ // get rewritten and the hub's per-module health probe ends up hitting
173
+ // `/vault/default/health` against a vault that lives at `/vault/<other>/`,
174
+ // returning 404 even when the vault is healthy. Build health off paths[0]
175
+ // so the path follows the primary vault by construction. Closes vault#369.
176
+ const primaryPath = paths[0] ?? "/vault/default";
177
+ const health = `${primaryPath}/health`;
178
+
166
179
  // Build the entry with manifest-sourced metadata (displayName, tagline,
167
180
  // stripPrefix) layered on top of the operationally-determined fields
168
181
  // (port from config, paths from current vault list, version from
@@ -178,7 +191,7 @@ export function selfRegister(deps: SelfRegisterDeps): SelfRegisterResult {
178
191
  name: manifest.manifestName,
179
192
  port,
180
193
  paths,
181
- health: manifest.health,
194
+ health,
182
195
  version: deps.version,
183
196
  installDir,
184
197
  };
@@ -212,7 +225,7 @@ export function selfRegister(deps: SelfRegisterDeps): SelfRegisterResult {
212
225
  priorRow.version !== deps.version ||
213
226
  priorRow.port !== port ||
214
227
  JSON.stringify(priorRow.paths) !== JSON.stringify(paths) ||
215
- priorRow.health !== manifest.health ||
228
+ priorRow.health !== health ||
216
229
  (priorRow as { displayName?: string }).displayName !== manifest.displayName ||
217
230
  (priorRow as { tagline?: string }).tagline !== manifest.tagline ||
218
231
  (priorRow as { stripPrefix?: boolean }).stripPrefix !== manifest.stripPrefix;