@forgezero/vault 0.1.1 → 0.1.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/dist/index.d.ts CHANGED
@@ -16,12 +16,13 @@
16
16
  * places, and the stronger posture is the one requiring less configuration
17
17
  * rather than more.
18
18
  *
19
- * ## Node assignment
19
+ * ## Stable API origin
20
20
  *
21
- * A directory answers which node to use; the client then talks to it directly
22
- * for a short window. Reassignment includes 530 a node whose TUNNEL is down
23
- * returns Cloudflare 1033, not an application status, so a retry list of only
24
- * 5xx misses it entirely.
21
+ * External clients always call one API origin. Node selection and failover are
22
+ * edge responsibilities; exposing node hostnames here would couple every SDK
23
+ * to platform topology and can route a tenant to a process that does not hold
24
+ * its realm seed. Managed applications continue to use the local agent socket
25
+ * and make no remote request at all.
25
26
  *
26
27
  * This package must not import `@forgezero/providers` either. `vaultCredentials`
27
28
  * returns the shape structurally, because a dependency edge in either direction
@@ -104,23 +105,9 @@ export type AgentResponse = {
104
105
  };
105
106
  };
106
107
  export type AgentTransport = (socketPath: string, request: AgentRequest) => Promise<AgentResponse>;
107
- export interface Assignment {
108
- node: string;
109
- ttl: number;
110
- expiresAt: number;
111
- }
112
- /**
113
- * Statuses meaning "ask for a different node", not "this failed".
114
- *
115
- * 530 is the one people miss: a node whose tunnel is down never reaches the
116
- * application, so Cloudflare answers 1033 with 530.
117
- */
118
- export declare const REASSIGN_ON: Set<number>;
119
108
  export interface VaultOptions {
120
- /** Directory endpoint. Unauthenticated a node hostname is not a secret. */
121
- assignUrl?: string;
122
- /** Override only for a private ForgeZero installation with its own node domain. */
123
- nodeAllowed?: (hostname: string) => boolean;
109
+ /** Stable public API origin. Node topology is never exposed to the client. */
110
+ apiUrl?: string;
124
111
  requestTimeoutMs?: number;
125
112
  project?: string;
126
113
  environment?: string;
@@ -129,7 +116,6 @@ export interface VaultOptions {
129
116
  agentTransport?: AgentTransport;
130
117
  credential?: Credential;
131
118
  discovery?: DiscoveryEnvironment;
132
- now?: () => number;
133
119
  }
134
120
  export interface EntryMeta {
135
121
  name: string;
@@ -142,27 +128,18 @@ export interface Change {
142
128
  name: string;
143
129
  version: number;
144
130
  }
145
- export declare const DEFAULT_ASSIGN_URL = "https://assign.forgezero.net/v1/assign";
131
+ export declare const DEFAULT_API_URL = "https://api.forgezero.net";
146
132
  export declare const DEFAULT_REQUEST_TIMEOUT_MS = 15000;
147
- /** A compromised directory must not turn a signed secret read into an open relay. */
148
- export declare const isForgeZeroNode: (hostname: string) => boolean;
149
133
  export declare class ForgeZero {
150
134
  readonly credential: Credential;
151
- private assignment;
152
- private readonly assignUrl;
135
+ private readonly apiUrl;
153
136
  private readonly doFetch;
154
- private readonly now;
155
137
  private readonly signer?;
156
138
  private readonly agentTransport;
157
- private readonly nodeAllowed;
158
139
  private readonly requestTimeoutMs;
159
140
  private readonly project;
160
141
  private readonly environment;
161
142
  constructor(options?: VaultOptions);
162
- /** The node to talk to, asking the directory only when the lease expired. */
163
- node(): Promise<string>;
164
- /** Drop the lease so the next call asks the directory again. */
165
- reassign(): void;
166
143
  private scope;
167
144
  private request;
168
145
  private managedRequest;
@@ -296,4 +273,4 @@ export declare function systemdCredentials(options?: SystemdCredentialOptions):
296
273
  readonly name: 'systemd';
297
274
  get(reference: string, field: string): Promise<string>;
298
275
  };
299
- export declare const VERSION = "0.1.1";
276
+ export declare const VERSION = "0.1.2";
package/dist/index.js CHANGED
@@ -110,32 +110,37 @@ var requestAgent = (socketPath, request) => new Promise((resolveRequest, reject)
110
110
  });
111
111
  socket.on("error", (cause) => reject(new VaultError("AGENT_UNAVAILABLE", cause.message)));
112
112
  });
113
- var REASSIGN_ON = new Set([410, 421, 502, 503, 504, 530]);
114
- var DEFAULT_ASSIGN_URL = "https://assign.forgezero.net/v1/assign";
113
+ var DEFAULT_API_URL = "https://api.forgezero.net";
115
114
  var DEFAULT_REQUEST_TIMEOUT_MS = 15000;
116
- var isForgeZeroNode = (hostname) => /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+forgezero\.net$/.test(hostname);
115
+ function apiOrigin(value) {
116
+ let url;
117
+ try {
118
+ url = new URL(value);
119
+ } catch {
120
+ throw new VaultError("API_URL_INVALID", "apiUrl must be an absolute HTTPS origin.");
121
+ }
122
+ const local = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1";
123
+ if (url.protocol !== "https:" && !(local && url.protocol === "http:") || url.username || url.password || url.search || url.hash || url.pathname !== "/" && url.pathname !== "")
124
+ throw new VaultError("API_URL_INVALID", "apiUrl must be an HTTPS origin without credentials, path, query or fragment.");
125
+ return url;
126
+ }
117
127
 
118
128
  class ForgeZero {
119
129
  credential;
120
- assignment;
121
- assignUrl;
130
+ apiUrl;
122
131
  doFetch;
123
- now;
124
132
  signer;
125
133
  agentTransport;
126
- nodeAllowed;
127
134
  requestTimeoutMs;
128
135
  project;
129
136
  environment;
130
137
  constructor(options = {}) {
131
138
  const credential = options.credential ?? discover(options.discovery);
132
139
  this.credential = credential.mode === "managed" && !credential.socketPath ? { ...credential, socketPath: DEFAULT_SOCKET } : credential;
133
- this.assignUrl = options.assignUrl ?? DEFAULT_ASSIGN_URL;
140
+ this.apiUrl = apiOrigin(options.apiUrl ?? DEFAULT_API_URL);
134
141
  this.doFetch = options.fetch ?? globalThis.fetch;
135
- this.now = options.now ?? Date.now;
136
142
  this.signer = options.signer ?? (this.credential.mode === "external" ? signerFromApiKey(this.credential.apiKey) : undefined);
137
143
  this.agentTransport = options.agentTransport ?? requestAgent;
138
- this.nodeAllowed = options.nodeAllowed ?? isForgeZeroNode;
139
144
  this.requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
140
145
  if (!Number.isFinite(this.requestTimeoutMs) || this.requestTimeoutMs < 100 || this.requestTimeoutMs > 120000) {
141
146
  throw new VaultError("INVALID_TIMEOUT", "requestTimeoutMs must be between 100 and 120000 milliseconds.");
@@ -143,45 +148,14 @@ class ForgeZero {
143
148
  this.project = options.project ?? "default";
144
149
  this.environment = options.environment ?? "production";
145
150
  }
146
- async node() {
147
- const now = this.now();
148
- if (this.assignment && this.assignment.expiresAt > now)
149
- return this.assignment.node;
150
- const assignmentUrl = new URL(this.assignUrl);
151
- if (this.signer?.keyId && !assignmentUrl.searchParams.has("client")) {
152
- assignmentUrl.searchParams.set("client", this.signer.keyId);
153
- }
154
- const response = await this.doFetch(assignmentUrl, {
155
- method: "GET",
156
- headers: { accept: "application/json" },
157
- signal: AbortSignal.timeout(this.requestTimeoutMs)
158
- });
159
- if (!response.ok)
160
- throw new VaultError("NO_NODE", "No vault node is available right now.");
161
- const payload = await response.json().catch(() => null);
162
- const hostname = typeof payload?.node === "string" ? payload.node : payload?.node && typeof payload.node === "object" && ("hostname" in payload.node) ? payload.node.hostname : undefined;
163
- if (typeof hostname !== "string" || !this.nodeAllowed(hostname)) {
164
- throw new VaultError("UNTRUSTED_NODE", "The directory returned an untrusted vault node hostname.");
165
- }
166
- const ttl = payload?.ttl ?? 60;
167
- if (typeof ttl !== "number" || !Number.isFinite(ttl) || ttl < 1 || ttl > 300) {
168
- throw new VaultError("INVALID_ASSIGNMENT", "The directory returned an invalid vault-node lease.");
169
- }
170
- this.assignment = { node: hostname, ttl, expiresAt: now + ttl * 1000 };
171
- return hostname;
172
- }
173
- reassign() {
174
- this.assignment = undefined;
175
- }
176
151
  scope() {
177
152
  return `${encodeURIComponent(this.project)}/${encodeURIComponent(this.environment)}`;
178
153
  }
179
- async request(path, init = {}, retried = false) {
154
+ async request(path, init = {}) {
180
155
  if (this.credential.mode === "managed")
181
156
  return this.managedRequest(path, init);
182
- const node = await this.node();
183
157
  const body = typeof init.body === "string" ? init.body : "";
184
- const target = new URL(path, "https://vault.invalid");
158
+ const target = new URL(path, this.apiUrl);
185
159
  const headers = {
186
160
  "content-type": "application/json",
187
161
  ...init.headers
@@ -195,15 +169,11 @@ class ForgeZero {
195
169
  body
196
170
  });
197
171
  }
198
- const response = await this.doFetch(`https://${node}${path}`, {
172
+ const response = await this.doFetch(target, {
199
173
  ...init,
200
174
  headers,
201
175
  signal: init.signal ?? AbortSignal.timeout(this.requestTimeoutMs)
202
176
  });
203
- if (REASSIGN_ON.has(response.status) && !retried) {
204
- this.reassign();
205
- return this.request(path, init, true);
206
- }
207
177
  const payload = await response.json().catch(() => {
208
178
  return;
209
179
  });
@@ -362,20 +332,18 @@ function systemdCredentials(options = {}) {
362
332
  }
363
333
  };
364
334
  }
365
- var VERSION = "0.1.1";
335
+ var VERSION = "0.1.2";
366
336
  export {
367
337
  vaultCredentials,
368
338
  systemdCredentials,
369
339
  signerFromApiKey,
370
- isForgeZeroNode,
371
340
  discover,
372
341
  directCredentials,
373
342
  createVault,
374
343
  VaultError,
375
344
  VERSION,
376
- REASSIGN_ON,
377
345
  ForgeZero,
378
346
  DEFAULT_SOCKET,
379
347
  DEFAULT_REQUEST_TIMEOUT_MS,
380
- DEFAULT_ASSIGN_URL
348
+ DEFAULT_API_URL
381
349
  };
package/dist/providers.js CHANGED
@@ -110,32 +110,37 @@ var requestAgent = (socketPath, request) => new Promise((resolveRequest, reject)
110
110
  });
111
111
  socket.on("error", (cause) => reject(new VaultError("AGENT_UNAVAILABLE", cause.message)));
112
112
  });
113
- var REASSIGN_ON = new Set([410, 421, 502, 503, 504, 530]);
114
- var DEFAULT_ASSIGN_URL = "https://assign.forgezero.net/v1/assign";
113
+ var DEFAULT_API_URL = "https://api.forgezero.net";
115
114
  var DEFAULT_REQUEST_TIMEOUT_MS = 15000;
116
- var isForgeZeroNode = (hostname) => /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+forgezero\.net$/.test(hostname);
115
+ function apiOrigin(value) {
116
+ let url;
117
+ try {
118
+ url = new URL(value);
119
+ } catch {
120
+ throw new VaultError("API_URL_INVALID", "apiUrl must be an absolute HTTPS origin.");
121
+ }
122
+ const local = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1";
123
+ if (url.protocol !== "https:" && !(local && url.protocol === "http:") || url.username || url.password || url.search || url.hash || url.pathname !== "/" && url.pathname !== "")
124
+ throw new VaultError("API_URL_INVALID", "apiUrl must be an HTTPS origin without credentials, path, query or fragment.");
125
+ return url;
126
+ }
117
127
 
118
128
  class ForgeZero {
119
129
  credential;
120
- assignment;
121
- assignUrl;
130
+ apiUrl;
122
131
  doFetch;
123
- now;
124
132
  signer;
125
133
  agentTransport;
126
- nodeAllowed;
127
134
  requestTimeoutMs;
128
135
  project;
129
136
  environment;
130
137
  constructor(options = {}) {
131
138
  const credential = options.credential ?? discover(options.discovery);
132
139
  this.credential = credential.mode === "managed" && !credential.socketPath ? { ...credential, socketPath: DEFAULT_SOCKET } : credential;
133
- this.assignUrl = options.assignUrl ?? DEFAULT_ASSIGN_URL;
140
+ this.apiUrl = apiOrigin(options.apiUrl ?? DEFAULT_API_URL);
134
141
  this.doFetch = options.fetch ?? globalThis.fetch;
135
- this.now = options.now ?? Date.now;
136
142
  this.signer = options.signer ?? (this.credential.mode === "external" ? signerFromApiKey(this.credential.apiKey) : undefined);
137
143
  this.agentTransport = options.agentTransport ?? requestAgent;
138
- this.nodeAllowed = options.nodeAllowed ?? isForgeZeroNode;
139
144
  this.requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
140
145
  if (!Number.isFinite(this.requestTimeoutMs) || this.requestTimeoutMs < 100 || this.requestTimeoutMs > 120000) {
141
146
  throw new VaultError("INVALID_TIMEOUT", "requestTimeoutMs must be between 100 and 120000 milliseconds.");
@@ -143,45 +148,14 @@ class ForgeZero {
143
148
  this.project = options.project ?? "default";
144
149
  this.environment = options.environment ?? "production";
145
150
  }
146
- async node() {
147
- const now = this.now();
148
- if (this.assignment && this.assignment.expiresAt > now)
149
- return this.assignment.node;
150
- const assignmentUrl = new URL(this.assignUrl);
151
- if (this.signer?.keyId && !assignmentUrl.searchParams.has("client")) {
152
- assignmentUrl.searchParams.set("client", this.signer.keyId);
153
- }
154
- const response = await this.doFetch(assignmentUrl, {
155
- method: "GET",
156
- headers: { accept: "application/json" },
157
- signal: AbortSignal.timeout(this.requestTimeoutMs)
158
- });
159
- if (!response.ok)
160
- throw new VaultError("NO_NODE", "No vault node is available right now.");
161
- const payload = await response.json().catch(() => null);
162
- const hostname = typeof payload?.node === "string" ? payload.node : payload?.node && typeof payload.node === "object" && ("hostname" in payload.node) ? payload.node.hostname : undefined;
163
- if (typeof hostname !== "string" || !this.nodeAllowed(hostname)) {
164
- throw new VaultError("UNTRUSTED_NODE", "The directory returned an untrusted vault node hostname.");
165
- }
166
- const ttl = payload?.ttl ?? 60;
167
- if (typeof ttl !== "number" || !Number.isFinite(ttl) || ttl < 1 || ttl > 300) {
168
- throw new VaultError("INVALID_ASSIGNMENT", "The directory returned an invalid vault-node lease.");
169
- }
170
- this.assignment = { node: hostname, ttl, expiresAt: now + ttl * 1000 };
171
- return hostname;
172
- }
173
- reassign() {
174
- this.assignment = undefined;
175
- }
176
151
  scope() {
177
152
  return `${encodeURIComponent(this.project)}/${encodeURIComponent(this.environment)}`;
178
153
  }
179
- async request(path, init = {}, retried = false) {
154
+ async request(path, init = {}) {
180
155
  if (this.credential.mode === "managed")
181
156
  return this.managedRequest(path, init);
182
- const node = await this.node();
183
157
  const body = typeof init.body === "string" ? init.body : "";
184
- const target = new URL(path, "https://vault.invalid");
158
+ const target = new URL(path, this.apiUrl);
185
159
  const headers = {
186
160
  "content-type": "application/json",
187
161
  ...init.headers
@@ -195,15 +169,11 @@ class ForgeZero {
195
169
  body
196
170
  });
197
171
  }
198
- const response = await this.doFetch(`https://${node}${path}`, {
172
+ const response = await this.doFetch(target, {
199
173
  ...init,
200
174
  headers,
201
175
  signal: init.signal ?? AbortSignal.timeout(this.requestTimeoutMs)
202
176
  });
203
- if (REASSIGN_ON.has(response.status) && !retried) {
204
- this.reassign();
205
- return this.request(path, init, true);
206
- }
207
177
  const payload = await response.json().catch(() => {
208
178
  return;
209
179
  });
@@ -362,7 +332,7 @@ function systemdCredentials(options = {}) {
362
332
  }
363
333
  };
364
334
  }
365
- var VERSION = "0.1.1";
335
+ var VERSION = "0.1.2";
366
336
 
367
337
  // src/providers.ts
368
338
  var MISSING = new Set(["ENTRY_NOT_FOUND", "VERSION_NOT_FOUND"]);
package/dist/schema.js CHANGED
@@ -110,32 +110,37 @@ var requestAgent = (socketPath, request) => new Promise((resolveRequest, reject)
110
110
  });
111
111
  socket.on("error", (cause) => reject(new VaultError("AGENT_UNAVAILABLE", cause.message)));
112
112
  });
113
- var REASSIGN_ON = new Set([410, 421, 502, 503, 504, 530]);
114
- var DEFAULT_ASSIGN_URL = "https://assign.forgezero.net/v1/assign";
113
+ var DEFAULT_API_URL = "https://api.forgezero.net";
115
114
  var DEFAULT_REQUEST_TIMEOUT_MS = 15000;
116
- var isForgeZeroNode = (hostname) => /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+forgezero\.net$/.test(hostname);
115
+ function apiOrigin(value) {
116
+ let url;
117
+ try {
118
+ url = new URL(value);
119
+ } catch {
120
+ throw new VaultError("API_URL_INVALID", "apiUrl must be an absolute HTTPS origin.");
121
+ }
122
+ const local = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1";
123
+ if (url.protocol !== "https:" && !(local && url.protocol === "http:") || url.username || url.password || url.search || url.hash || url.pathname !== "/" && url.pathname !== "")
124
+ throw new VaultError("API_URL_INVALID", "apiUrl must be an HTTPS origin without credentials, path, query or fragment.");
125
+ return url;
126
+ }
117
127
 
118
128
  class ForgeZero {
119
129
  credential;
120
- assignment;
121
- assignUrl;
130
+ apiUrl;
122
131
  doFetch;
123
- now;
124
132
  signer;
125
133
  agentTransport;
126
- nodeAllowed;
127
134
  requestTimeoutMs;
128
135
  project;
129
136
  environment;
130
137
  constructor(options = {}) {
131
138
  const credential = options.credential ?? discover(options.discovery);
132
139
  this.credential = credential.mode === "managed" && !credential.socketPath ? { ...credential, socketPath: DEFAULT_SOCKET } : credential;
133
- this.assignUrl = options.assignUrl ?? DEFAULT_ASSIGN_URL;
140
+ this.apiUrl = apiOrigin(options.apiUrl ?? DEFAULT_API_URL);
134
141
  this.doFetch = options.fetch ?? globalThis.fetch;
135
- this.now = options.now ?? Date.now;
136
142
  this.signer = options.signer ?? (this.credential.mode === "external" ? signerFromApiKey(this.credential.apiKey) : undefined);
137
143
  this.agentTransport = options.agentTransport ?? requestAgent;
138
- this.nodeAllowed = options.nodeAllowed ?? isForgeZeroNode;
139
144
  this.requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
140
145
  if (!Number.isFinite(this.requestTimeoutMs) || this.requestTimeoutMs < 100 || this.requestTimeoutMs > 120000) {
141
146
  throw new VaultError("INVALID_TIMEOUT", "requestTimeoutMs must be between 100 and 120000 milliseconds.");
@@ -143,45 +148,14 @@ class ForgeZero {
143
148
  this.project = options.project ?? "default";
144
149
  this.environment = options.environment ?? "production";
145
150
  }
146
- async node() {
147
- const now = this.now();
148
- if (this.assignment && this.assignment.expiresAt > now)
149
- return this.assignment.node;
150
- const assignmentUrl = new URL(this.assignUrl);
151
- if (this.signer?.keyId && !assignmentUrl.searchParams.has("client")) {
152
- assignmentUrl.searchParams.set("client", this.signer.keyId);
153
- }
154
- const response = await this.doFetch(assignmentUrl, {
155
- method: "GET",
156
- headers: { accept: "application/json" },
157
- signal: AbortSignal.timeout(this.requestTimeoutMs)
158
- });
159
- if (!response.ok)
160
- throw new VaultError("NO_NODE", "No vault node is available right now.");
161
- const payload = await response.json().catch(() => null);
162
- const hostname = typeof payload?.node === "string" ? payload.node : payload?.node && typeof payload.node === "object" && ("hostname" in payload.node) ? payload.node.hostname : undefined;
163
- if (typeof hostname !== "string" || !this.nodeAllowed(hostname)) {
164
- throw new VaultError("UNTRUSTED_NODE", "The directory returned an untrusted vault node hostname.");
165
- }
166
- const ttl = payload?.ttl ?? 60;
167
- if (typeof ttl !== "number" || !Number.isFinite(ttl) || ttl < 1 || ttl > 300) {
168
- throw new VaultError("INVALID_ASSIGNMENT", "The directory returned an invalid vault-node lease.");
169
- }
170
- this.assignment = { node: hostname, ttl, expiresAt: now + ttl * 1000 };
171
- return hostname;
172
- }
173
- reassign() {
174
- this.assignment = undefined;
175
- }
176
151
  scope() {
177
152
  return `${encodeURIComponent(this.project)}/${encodeURIComponent(this.environment)}`;
178
153
  }
179
- async request(path, init = {}, retried = false) {
154
+ async request(path, init = {}) {
180
155
  if (this.credential.mode === "managed")
181
156
  return this.managedRequest(path, init);
182
- const node = await this.node();
183
157
  const body = typeof init.body === "string" ? init.body : "";
184
- const target = new URL(path, "https://vault.invalid");
158
+ const target = new URL(path, this.apiUrl);
185
159
  const headers = {
186
160
  "content-type": "application/json",
187
161
  ...init.headers
@@ -195,15 +169,11 @@ class ForgeZero {
195
169
  body
196
170
  });
197
171
  }
198
- const response = await this.doFetch(`https://${node}${path}`, {
172
+ const response = await this.doFetch(target, {
199
173
  ...init,
200
174
  headers,
201
175
  signal: init.signal ?? AbortSignal.timeout(this.requestTimeoutMs)
202
176
  });
203
- if (REASSIGN_ON.has(response.status) && !retried) {
204
- this.reassign();
205
- return this.request(path, init, true);
206
- }
207
177
  const payload = await response.json().catch(() => {
208
178
  return;
209
179
  });
@@ -362,7 +332,7 @@ function systemdCredentials(options = {}) {
362
332
  }
363
333
  };
364
334
  }
365
- var VERSION = "0.1.1";
335
+ var VERSION = "0.1.2";
366
336
 
367
337
  // src/schema.ts
368
338
  function managedSchemas(vault, options = {}) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "//": "Publishing happens from an operator's machine, not CI \u2014 CLAUDE.md records that the absence of CI is deliberate. npm's `provenance` attests a tarball was built by a recognised CI provider from a named commit, so it cannot be produced here: it was set, and the first publish failed with `Automatic provenance generation not supported for provider: null`. A setting that can never be satisfied is worse than none, because it reads as a guarantee nobody is getting. Restore it the day this publishes from CI, and not before.",
3
3
  "name": "@forgezero/vault",
4
- "version": "0.1.1",
4
+ "version": "0.1.2",
5
5
  "type": "module",
6
6
  "publishConfig": {
7
7
  "access": "public"
@@ -49,7 +49,7 @@
49
49
  "@noble/post-quantum": "^0.6.1",
50
50
  "@noble/hashes": "^2.2.0"
51
51
  },
52
- "description": "ForgeZero vault client. Credential discovery, versioned secrets, node assignment.",
52
+ "description": "ForgeZero vault client. Credential discovery and versioned secrets through one stable API origin.",
53
53
  "keywords": [
54
54
  "secrets",
55
55
  "vault",