@openparachute/vault 0.5.2-rc.1 → 0.5.2-rc.3

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.
@@ -85,13 +85,15 @@ describe("vault create --json", () => {
85
85
  expect(lines).toHaveLength(1);
86
86
  const payload = JSON.parse(lines[0]!);
87
87
  expect(payload.name).toBe("myvault");
88
- // vault#282 Stage 2: vault no longer mints pvt_* tokens. The contract
89
- // hub's admin-vaults.ts requires still holds (`token` is a string). In
90
- // this sandbox there's no hub/operator.token, so no token is issued: the
91
- // token field is the empty string and `token_guidance` explains why.
88
+ // vault#442: default auth is per-user OAuth `create` does NOT mint a
89
+ // token. The contract hub's admin-vaults.ts requires still holds (`token`
90
+ // is a string); it's the empty string and `token_guidance` carries the
91
+ // OAuth-first connect path (the hub SPA handles the empty-token case and
92
+ // mints admin via its own session-cookie path).
92
93
  expect(typeof payload.token).toBe("string");
93
94
  expect(payload.token).toBe("");
94
- expect(payload.token_guidance).toContain("No token issued");
95
+ expect(payload.token_guidance).toContain("No token minted");
96
+ expect(payload.token_guidance).toContain("per-user OAuth");
95
97
  expect(payload.set_as_default).toBe(true);
96
98
  expect(payload.paths.vault_dir).toBe(join(home, "vault", "data", "myvault"));
97
99
  expect(payload.paths.vault_db).toBe(join(home, "vault", "data", "myvault", "vault.db"));
@@ -160,6 +162,95 @@ describe("vault create --json", () => {
160
162
  });
161
163
  });
162
164
 
165
+ /**
166
+ * vault#442: default to per-user OAuth — `create` must NOT auto-mint or bake in
167
+ * a shared `vault:<name>:admin` token. Token-minting is explicit opt-in
168
+ * (`--mint`) and scope-narrow (read/write, NEVER admin); `--token <bearer>` is
169
+ * the paste path. These tests pin the behavioral contract.
170
+ */
171
+ describe("vault create — OAuth-first auth (vault#442)", () => {
172
+ test("default create does NOT mint a token — empty token + OAuth guidance (--json)", () => {
173
+ const { exitCode, stdout } = runCli(["create", "oauthy", "--json"], {
174
+ PARACHUTE_HOME: home,
175
+ });
176
+ expect(exitCode).toBe(0);
177
+ const payload = JSON.parse(stdout.trim());
178
+ // No token baked in — OAuth on first connect.
179
+ expect(payload.token).toBe("");
180
+ expect(payload.token_guidance).toContain("No token minted");
181
+ expect(payload.token_guidance).toContain("per-user OAuth");
182
+ // Never the admin-mint failure copy.
183
+ expect(payload.token_guidance).not.toContain("No token issued");
184
+ expect(payload.token_guidance).not.toContain("admin");
185
+ });
186
+
187
+ test("default create leads the human summary with the OAuth connect command", () => {
188
+ const { exitCode, stdout } = runCli(["create", "connectme"], {
189
+ PARACHUTE_HOME: home,
190
+ });
191
+ expect(exitCode).toBe(0);
192
+ expect(stdout).toContain(
193
+ "Connect your AI: claude mcp add --transport http parachute-connectme",
194
+ );
195
+ expect(stdout).toContain("no token needed");
196
+ // Scope-narrow opt-in pointer, never admin.
197
+ expect(stdout).toContain("parachute auth mint-token --scope vault:connectme:read");
198
+ expect(stdout).not.toContain("vault:connectme:admin");
199
+ });
200
+
201
+ test("--scope admin is rejected from the create flow (admin never mintable here)", () => {
202
+ const { exitCode, stdout, stderr } = runCli(
203
+ ["create", "noadmin", "--mint", "--scope", "admin", "--json"],
204
+ { PARACHUTE_HOME: home },
205
+ );
206
+ expect(exitCode).not.toBe(0);
207
+ expect(stdout).toBe("");
208
+ expect(stderr).toContain('--scope must be "read" or "write"');
209
+ // The vault must not have been created (rejected before createVault).
210
+ expect(existsSync(join(home, "vault", "data", "noadmin", "vault.db"))).toBe(false);
211
+ });
212
+
213
+ test("--mint and --token are mutually exclusive", () => {
214
+ const { exitCode, stderr } = runCli(
215
+ ["create", "conflict", "--mint", "--token", "abc.def.ghi", "--json"],
216
+ { PARACHUTE_HOME: home },
217
+ );
218
+ expect(exitCode).not.toBe(0);
219
+ expect(stderr).toContain("mutually exclusive");
220
+ });
221
+
222
+ test("--token <bearer> paste path surfaces the supplied bearer (no mint attempted)", () => {
223
+ const { exitCode, stdout } = runCli(
224
+ ["create", "pasted", "--token", "header.auth.bearer", "--json"],
225
+ { PARACHUTE_HOME: home },
226
+ );
227
+ expect(exitCode).toBe(0);
228
+ const payload = JSON.parse(stdout.trim());
229
+ // The pasted bearer is surfaced verbatim — vault never minted one.
230
+ expect(payload.token).toBe("header.auth.bearer");
231
+ expect(payload.token_guidance).toContain("--token");
232
+ // No admin scope, no mint-failure copy.
233
+ expect(payload.token_guidance).not.toContain("No token issued");
234
+ });
235
+
236
+ test("--mint (no hub reachable) opts in but mints scope-narrow read, never admin", () => {
237
+ // In this sandbox there's no hub/operator.token, so the mint can't complete
238
+ // — but the request is scope-narrow read by default and must NEVER ask for
239
+ // admin. We assert the create still succeeds and the guidance points at the
240
+ // mint-token recovery (the scope requested is read, per mintBootstrapCredential).
241
+ const { exitCode, stdout } = runCli(
242
+ ["create", "wantmint", "--mint", "--json"],
243
+ { PARACHUTE_HOME: home },
244
+ );
245
+ expect(exitCode).toBe(0);
246
+ const payload = JSON.parse(stdout.trim());
247
+ // No hub here → no token, but the guidance is the standalone mint path, not
248
+ // an admin grant.
249
+ expect(payload.token).toBe("");
250
+ expect(payload.token_guidance).not.toContain("admin");
251
+ });
252
+ });
253
+
163
254
  /**
164
255
  * Regression tests for #208: `vault create` was not updating
165
256
  * `~/.parachute/services.json`, so vaults created after init were invisible
@@ -229,10 +320,14 @@ describe("vault create (human mode)", () => {
229
320
  );
230
321
  expect(exitCode).toBe(0);
231
322
  expect(stdout).toContain('Vault "human" created.');
232
- // vault#282 Stage 2: with no hub reachable in this sandbox, no token is
233
- // issued the human output prints the guidance instead of "API token:".
234
- expect(stdout).toContain("No token issued");
235
- expect(stdout).toContain("Install the hub");
323
+ // vault#442: default auth is per-user OAuth NO token is minted, even when
324
+ // a hub would have been reachable. The human output leads with the OAuth
325
+ // connect command and never prints an "API token:" line.
326
+ expect(stdout).toContain("No token minted");
327
+ expect(stdout).toContain("Connect your AI: claude mcp add --transport http parachute-human");
328
+ expect(stdout).not.toContain("API token:");
329
+ // The old admin auto-mint failure copy must NOT fire on a default create.
330
+ expect(stdout).not.toContain("No token issued");
236
331
  // Human output should NOT be valid JSON.
237
332
  expect(() => JSON.parse(stdout.trim())).toThrow();
238
333
  });