@fadhilp/stateql 0.10.1 → 0.11.1

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.
@@ -126,6 +126,11 @@ const MIGRATIONS = [
126
126
  requireIndexes(db, ["connections_alias"]);
127
127
  },
128
128
  },
129
+ {
130
+ name: "password_refs_v1",
131
+ apply: migratePasswordRefs,
132
+ validate: validatePasswordRefs,
133
+ },
129
134
  ];
130
135
  export function runMigrations(db, now) {
131
136
  db.exec(`
@@ -364,7 +369,11 @@ function validateSharedSessionActors(db) {
364
369
  function migrateCredentialRefs(db) {
365
370
  const profileColumns = new Set(db.prepare("PRAGMA table_info(profiles)").all().map((column) => column.name));
366
371
  const hasCredentialRef = profileColumns.has("credential_ref");
372
+ const hasPasswordRef = profileColumns.has("password_ref");
367
373
  if (!profileCredentialRefSchemaCurrent(db)) {
374
+ if (hasPasswordRef) {
375
+ throw new Error("State migration cannot safely rebuild a profile schema that already contains password references.");
376
+ }
368
377
  const missingSources = db.prepare(`SELECT COUNT(*) AS count FROM profiles
369
378
  WHERE target IS NULL AND secret_env IS NULL${hasCredentialRef ? " AND credential_ref IS NULL" : ""}`).get();
370
379
  if (missingSources.count) {
@@ -424,6 +433,117 @@ function profileCredentialRefSchemaCurrent(db) {
424
433
  const row = db.prepare("SELECT sql FROM sqlite_schema WHERE type = 'table' AND name = 'profiles'").get();
425
434
  return Boolean(row?.sql && /CHECK\s*\(\s*\(target IS NOT NULL\)\s*\+\s*\(secret_env IS NOT NULL\)\s*\+\s*\(credential_ref IS NOT NULL\)\s*=\s*1\s*\)/i.test(row.sql));
426
435
  }
436
+ function migratePasswordRefs(db) {
437
+ const profileColumns = tableColumns(db, "profiles");
438
+ const profilePasswordRef = profileColumns.has("password_ref") ? "password_ref" : "NULL";
439
+ if (!profilePasswordRefSchemaCurrent(db)) {
440
+ const invalid = db.prepare(`SELECT COUNT(*) AS count FROM profiles
441
+ WHERE ${profilePasswordRef} IS NOT NULL AND target IS NULL`).get();
442
+ if (invalid.count) {
443
+ throw new Error("State migration found a password reference without a target profile source.");
444
+ }
445
+ db.exec(`
446
+ ALTER TABLE profiles RENAME TO profiles_legacy_password_refs_v1;
447
+ CREATE TABLE profiles (
448
+ name TEXT PRIMARY KEY,
449
+ target TEXT,
450
+ secret_env TEXT,
451
+ credential_ref TEXT,
452
+ password_ref TEXT,
453
+ read_only INTEGER NOT NULL,
454
+ created_at TEXT NOT NULL,
455
+ updated_at TEXT NOT NULL,
456
+ CHECK (
457
+ (target IS NOT NULL) +
458
+ (secret_env IS NOT NULL) +
459
+ (credential_ref IS NOT NULL) = 1
460
+ ),
461
+ CHECK (password_ref IS NULL OR target IS NOT NULL)
462
+ );
463
+ INSERT INTO profiles
464
+ (name, target, secret_env, credential_ref, password_ref, read_only, created_at, updated_at)
465
+ SELECT name, target, secret_env, credential_ref, ${profilePasswordRef}, read_only, created_at, updated_at
466
+ FROM profiles_legacy_password_refs_v1;
467
+ DROP TABLE profiles_legacy_password_refs_v1;
468
+ `);
469
+ }
470
+ const connectionColumns = tableColumns(db, "connections");
471
+ const connectionPasswordRef = connectionColumns.has("password_ref") ? "password_ref" : "NULL";
472
+ if (!connectionPasswordRefSchemaCurrent(db)) {
473
+ const invalid = db.prepare(`SELECT COUNT(*) AS count FROM connections
474
+ WHERE ${connectionPasswordRef} IS NOT NULL
475
+ AND (secret_env IS NOT NULL OR credential_ref IS NOT NULL OR
476
+ driver NOT IN ('postgres', 'mysql', 'mongodb', 'redis'))`).get();
477
+ if (invalid.count) {
478
+ throw new Error("State migration found a password reference on a non-target connection source.");
479
+ }
480
+ db.exec(`
481
+ ALTER TABLE connections RENAME TO connections_legacy_password_refs_v1;
482
+ CREATE TABLE connections (
483
+ id TEXT PRIMARY KEY,
484
+ session_id TEXT NOT NULL,
485
+ name TEXT NOT NULL,
486
+ driver TEXT NOT NULL,
487
+ database_name TEXT NOT NULL,
488
+ source TEXT NOT NULL,
489
+ secret_env TEXT,
490
+ credential_ref TEXT,
491
+ password_ref TEXT,
492
+ read_only INTEGER NOT NULL,
493
+ version INTEGER NOT NULL,
494
+ created_at TEXT NOT NULL,
495
+ alias TEXT,
496
+ CHECK (
497
+ password_ref IS NULL OR
498
+ (secret_env IS NULL AND credential_ref IS NULL AND
499
+ driver IN ('postgres', 'mysql', 'mongodb', 'redis'))
500
+ ),
501
+ FOREIGN KEY(session_id) REFERENCES sessions(id)
502
+ );
503
+ INSERT INTO connections
504
+ (id, session_id, name, driver, database_name, source, secret_env,
505
+ credential_ref, password_ref, read_only, version, created_at, alias)
506
+ SELECT id, session_id, name, driver, database_name, source, secret_env,
507
+ credential_ref, ${connectionPasswordRef}, read_only, version, created_at, alias
508
+ FROM connections_legacy_password_refs_v1;
509
+ DROP TABLE connections_legacy_password_refs_v1;
510
+ CREATE UNIQUE INDEX connections_alias ON connections(alias);
511
+ `);
512
+ }
513
+ }
514
+ function validatePasswordRefs(db) {
515
+ requireColumns(db, "profiles", ["password_ref"]);
516
+ requireColumns(db, "connections", ["password_ref"]);
517
+ if (!profilePasswordRefSchemaCurrent(db) || !connectionPasswordRefSchemaCurrent(db)) {
518
+ throw new Error("State migration did not enforce password-reference source constraints.");
519
+ }
520
+ const invalidProfiles = db.prepare(`SELECT COUNT(*) AS count FROM profiles
521
+ WHERE password_ref IS NOT NULL AND target IS NULL`).get();
522
+ const invalidConnections = db.prepare(`SELECT COUNT(*) AS count FROM connections
523
+ WHERE password_ref IS NOT NULL
524
+ AND (secret_env IS NOT NULL OR credential_ref IS NOT NULL OR
525
+ driver NOT IN ('postgres', 'mysql', 'mongodb', 'redis'))`).get();
526
+ if (invalidProfiles.count || invalidConnections.count) {
527
+ throw new Error("State migration left an invalid password-reference source.");
528
+ }
529
+ }
530
+ function profilePasswordRefSchemaCurrent(db) {
531
+ const sql = tableSql(db, "profiles");
532
+ return profileCredentialRefSchemaCurrent(db) &&
533
+ /CHECK\s*\(\s*password_ref IS NULL OR target IS NOT NULL\s*\)/i.test(sql);
534
+ }
535
+ function connectionPasswordRefSchemaCurrent(db) {
536
+ return /CHECK\s*\(\s*password_ref IS NULL OR\s*\(secret_env IS NULL AND credential_ref IS NULL AND\s*driver IN \('postgres', 'mysql', 'mongodb', 'redis'\)\)\s*\)/i
537
+ .test(tableSql(db, "connections"));
538
+ }
539
+ function tableSql(db, table) {
540
+ const row = db.prepare("SELECT sql FROM sqlite_schema WHERE type = 'table' AND name = ?").get(table);
541
+ return row?.sql ?? "";
542
+ }
543
+ function tableColumns(db, table) {
544
+ return new Set(db.prepare(`PRAGMA table_info(${table})`).all()
545
+ .map((column) => column.name));
546
+ }
427
547
  function addColumn(db, table, column, definition) {
428
548
  const columns = db.prepare(`PRAGMA table_info(${table})`).all();
429
549
  if (!columns.some((candidate) => candidate.name === column)) {
@@ -14,6 +14,7 @@ export function profileData(profile) {
14
14
  target: profile.target,
15
15
  secret_env: profile.secret_env,
16
16
  credential_ref: profile.credential_ref,
17
+ password_ref: profile.password_ref,
17
18
  read_only: Boolean(profile.read_only),
18
19
  };
19
20
  }
@@ -1,7 +1,11 @@
1
1
  import { type TableChange } from "./table-editor.js";
2
- import type { ActorLinkData, ActorResolutionData, ActorsData, ActorUnlinkData, AliasData, ApplyData, BatchCommand, CommandExecutionContext, CommandOrigin, BatchOptions, CapabilitiesData, CatalogObject, DescribeObjectData, ListObjectsData, ListObjectsFilter, CloseSessionData, ColumnsData, CommitTransactionData, ConnectOptions, ConnectionData, CountData, DisconnectData, DoctorData, ExecData, ExecOptions, ExecutionOptions, ExportData, FilterOptions, HistoryData, HistoryOptions, OperationData, PlanData, PlanOptions, ProfileData, ProfilesData, ProfileOptions, ProfileUpdateOptions, MongoExecOptions, MongoPlanOptions, MongoQueryOptions, MongoReadCommand, MongoWriteCommand, RedisCommand, RedisExecOptions, RedisPlanOptions, RedisQueryOptions, PurgeData, QueryOptions, RemovedProfileData, Response, ResultData, RollbackTransactionData, RowsData, RowsOptions, Column, StateQLActorOptions, StateQLOptions, StateQLSnapshot, StateQLSnapshotOptions, StatusData, SessionData, SessionsData, SessionSummaryData, TransactionData } from "./types.js";
2
+ import type { ActorLinkData, ActorResolutionData, ActorsData, ActorUnlinkData, AliasData, ApplyData, BatchCommand, CommandExecutionContext, CommandOrigin, BatchOptions, CapabilitiesData, CatalogObject, DescribeObjectData, ListObjectsData, ListObjectsFilter, CloseSessionData, ColumnsData, CommitTransactionData, ConnectOptions, ConnectionData, CountData, DisconnectData, DoctorData, ExecData, ExecOptions, ExecutionOptions, ExportData, FilterOptions, HistoryData, HistoryOptions, OperationData, PlanData, PlanOptions, ProfileData, ProfilesData, ProfileOptions, ProfileUpdateOptions, MongoExecOptions, MongoPlanOptions, MongoQueryOptions, MongoReadCommand, MongoWriteCommand, RedisCommand, RedisExecOptions, RedisPlanOptions, RedisQueryOptions, PurgeData, QueryOptions, RemovedProfileData, Response, ResultData, RollbackTransactionData, RowsData, RowsOptions, Column, StateQLActorOptions, StateQLOptions, StateQLWorkspaceOptions, StateQLSnapshot, StateQLSnapshotOptions, StatusData, SessionData, SessionsData, SessionSummaryData, TransactionData } from "./types.js";
3
3
  export declare class StateQL {
4
+ /** Runtime contract marker for passwordRef/password_ref support. */
5
+ static readonly passwordReferenceVersion: 1;
4
6
  static forActor(options: StateQLActorOptions): StateQL;
7
+ /** Opens one actor in a named shared workspace for a trusted library host. */
8
+ static forWorkspace(options: StateQLWorkspaceOptions): StateQL;
5
9
  private readonly store;
6
10
  private readonly sessionName;
7
11
  private readonly actorId;
@@ -5,7 +5,7 @@ import { writeFileSync } from "node:fs";
5
5
  import { basename, resolve } from "node:path";
6
6
  import { env } from "node:process";
7
7
  import { AdapterExecutionError, AdapterWriteError, BatchWriteError, createAdapter, createAdapterContext, } from "./adapters.js";
8
- import { confidence, credentialSource, databaseIdentity, databaseUrlHasSecret, detectDriver, isEnvironmentName, mongoDatabaseName, redisDatabaseName, normalizeSqliteSource, validateCredentialRef, validateProfileName, version, } from "./connection.js";
8
+ import { confidence, credentialSource, databaseIdentity, databaseUrlHasSecret, detectDriver, isEnvironmentName, injectPassword, mongoDatabaseName, redisDatabaseName, normalizeSqliteSource, validateCredentialRef, validatePasswordReferenceTarget, validateProfileName, version, } from "./connection.js";
9
9
  import { asStateQLError, CredentialResolutionError, StateQLError, } from "./errors.js";
10
10
  import { analyzeMongoWriteSafety, deserializeMongoWriteCommand, serializeMongoCommand, validateMongoReadCommand, validateMongoWriteCommand, MongoAdapter, } from "./mongodb.js";
11
11
  import { deserializeRedisCommand, RedisAdapter, serializeRedisCommand, validateRedisReadCommand, validateRedisWriteCommand, } from "./redis.js";
@@ -17,7 +17,10 @@ import { compactRows, defaultHome, hash, isSqlParameters, parseJson, redact, } f
17
17
  const DEFAULT_SNAPSHOT_HISTORY_LIMIT = 50;
18
18
  const MAX_SNAPSHOT_HISTORY_LIMIT = 100;
19
19
  const DEFAULT_CREDENTIAL_RESOLUTION_TIMEOUT_MS = 120_000;
20
+ const WORKSPACE_BOOTSTRAP = Symbol("StateQL.workspaceBootstrap");
20
21
  export class StateQL {
22
+ /** Runtime contract marker for passwordRef/password_ref support. */
23
+ static passwordReferenceVersion = 1;
21
24
  static forActor(options) {
22
25
  if (!options.actor.trim()) {
23
26
  throw new StateQLError("INVALID_COMMAND", "Actor ID is required.");
@@ -35,6 +38,21 @@ export class StateQL {
35
38
  store.close();
36
39
  }
37
40
  }
41
+ /** Opens one actor in a named shared workspace for a trusted library host. */
42
+ static forWorkspace(options) {
43
+ if (!options.workspace.trim()) {
44
+ throw new StateQLError("INVALID_COMMAND", "Workspace name is required.");
45
+ }
46
+ if (!options.actor.trim()) {
47
+ throw new StateQLError("INVALID_COMMAND", "Actor ID is required.");
48
+ }
49
+ const { workspace, ...actorOptions } = options;
50
+ return new StateQL({
51
+ ...actorOptions,
52
+ session: workspace,
53
+ [WORKSPACE_BOOTSTRAP]: true,
54
+ });
55
+ }
38
56
  store;
39
57
  sessionName;
40
58
  actorId;
@@ -78,7 +96,12 @@ export class StateQL {
78
96
  }
79
97
  const store = new StateStore(options.home ?? defaultHome(), this.now, maxStateBytes);
80
98
  try {
81
- store.bootstrapSession(this.sessionName, this.actorId, options.actor === undefined);
99
+ if (options[WORKSPACE_BOOTSTRAP]) {
100
+ store.bootstrapWorkspace(this.sessionName, this.actorId);
101
+ }
102
+ else {
103
+ store.bootstrapSession(this.sessionName, this.actorId, options.actor === undefined);
104
+ }
82
105
  }
83
106
  catch (error) {
84
107
  store.close();
@@ -112,7 +135,13 @@ export class StateQL {
112
135
  if (sourceCount !== 1) {
113
136
  throw new StateQLError("INVALID_COMMAND", "Use exactly one connection target, profile, secret environment variable, or credential reference.");
114
137
  }
115
- const implicitProfile = !options.profile && !options.secretEnv && !options.credentialRef && target
138
+ if (options.passwordRef !== undefined &&
139
+ (target === undefined || options.profile !== undefined ||
140
+ options.secretEnv !== undefined || options.credentialRef !== undefined)) {
141
+ throw new StateQLError("INVALID_COMMAND", "A password reference requires a literal remote connection target.");
142
+ }
143
+ const implicitProfile = !options.profile && !options.secretEnv && !options.credentialRef &&
144
+ options.passwordRef === undefined && target
116
145
  ? this.store.getProfile(target)
117
146
  : undefined;
118
147
  const profile = options.profile
@@ -122,18 +151,27 @@ export class StateQL {
122
151
  throw new StateQLError("CONNECTION_NOT_FOUND", `Profile "${options.profile}" was not found.`, { suggestedAction: "Run stql profile list." });
123
152
  }
124
153
  if (profile &&
125
- [profile.target, profile.secret_env, profile.credential_ref]
126
- .filter((value) => value !== null).length !== 1) {
127
- throw new StateQLError("STATE_CORRUPTED", `Profile "${profile.name}" does not have exactly one connection source.`);
154
+ ([profile.target, profile.secret_env, profile.credential_ref]
155
+ .filter((value) => value !== null).length !== 1 ||
156
+ (profile.password_ref !== null && profile.target === null))) {
157
+ throw new StateQLError("STATE_CORRUPTED", `Profile "${profile.name}" has an invalid connection source.`);
128
158
  }
129
159
  const resolvedTarget = profile?.target ?? target;
130
160
  const secretEnv = options.secretEnv ?? profile?.secret_env ?? undefined;
131
161
  const credentialRef = options.credentialRef ?? profile?.credential_ref ?? undefined;
162
+ const passwordRef = options.passwordRef ?? profile?.password_ref ?? undefined;
132
163
  if (secretEnv !== undefined && !isEnvironmentName(secretEnv)) {
133
164
  throw new StateQLError("INVALID_COMMAND", "Secret environment variable name is invalid.");
134
165
  }
135
166
  if (credentialRef !== undefined)
136
167
  validateCredentialRef(credentialRef);
168
+ if (passwordRef !== undefined) {
169
+ validateCredentialRef(passwordRef);
170
+ if (!resolvedTarget) {
171
+ throw new StateQLError("INVALID_COMMAND", "A password reference requires a literal remote connection target.");
172
+ }
173
+ validatePasswordReferenceTarget(resolvedTarget);
174
+ }
137
175
  const credentialReference = secretEnv ?? credentialRef;
138
176
  const credentialReferenceSource = secretEnv !== undefined
139
177
  ? "secret_env"
@@ -141,50 +179,65 @@ export class StateQL {
141
179
  const readOnly = options.readOnly ??
142
180
  (profile ? Boolean(profile.read_only) : true);
143
181
  const context = this.executionContext(options);
144
- const secret = credentialReference && credentialReferenceSource
145
- ? await this.resolveCredential(credentialReference, credentialReferenceSource, session, "connect", readOnly ? "read" : "write", context, {
182
+ let adapterSource;
183
+ let driver;
184
+ if (passwordRef !== undefined) {
185
+ const password = await this.resolveCredential(passwordRef, "password_ref", session, "connect", readOnly ? "read" : "write", context, {
146
186
  ...(profile ? { profile: { name: profile.name } } : {}),
147
187
  requestedReadOnly: readOnly,
148
- })
149
- : resolvedTarget;
150
- if (!secret) {
151
- throw new StateQLError("INVALID_COMMAND", "Connection target is required.");
152
- }
153
- const resolvedSource = credentialReferenceSource
154
- ? credentialSource(secret, undefined, credentialReferenceSource)
155
- : { driver: detectDriver(secret), source: secret };
156
- const { driver } = resolvedSource;
157
- if (driver !== "sqlite" &&
158
- !credentialReference &&
159
- databaseUrlHasSecret(secret)) {
160
- throw new StateQLError("PERMISSION_DENIED", `Credential-bearing ${databaseDisplayName(driver)} URLs must use --env or --credential-ref.`, {
161
- suggestedAction: "Use an environment variable or trusted host credential reference.",
162
- });
188
+ }, resolvedTarget);
189
+ ({ driver, source: adapterSource } = injectPassword(resolvedTarget, password));
163
190
  }
164
- const adapterSource = credentialReferenceSource
165
- ? resolvedSource.source
166
- : driver === "sqlite" ? normalizeSqliteSource(secret) : secret;
167
- const source = driver === "sqlite"
168
- ? adapterSource
169
- : credentialReferenceSource
170
- ? redact(secret)
171
- : adapterSource;
191
+ else {
192
+ const secret = credentialReference && credentialReferenceSource
193
+ ? await this.resolveCredential(credentialReference, credentialReferenceSource, session, "connect", readOnly ? "read" : "write", context, {
194
+ ...(profile ? { profile: { name: profile.name } } : {}),
195
+ requestedReadOnly: readOnly,
196
+ })
197
+ : resolvedTarget;
198
+ if (!secret) {
199
+ throw new StateQLError("INVALID_COMMAND", "Connection target is required.");
200
+ }
201
+ const resolvedSource = credentialReferenceSource
202
+ ? credentialSource(secret, undefined, credentialReferenceSource)
203
+ : { driver: detectDriver(secret), source: secret };
204
+ driver = resolvedSource.driver;
205
+ if (driver !== "sqlite" &&
206
+ !credentialReference &&
207
+ databaseUrlHasSecret(secret)) {
208
+ throw new StateQLError("PERMISSION_DENIED", `Credential-bearing ${databaseDisplayName(driver)} URLs must use --env or --credential-ref.`, {
209
+ suggestedAction: "Use an environment variable or trusted host credential reference.",
210
+ });
211
+ }
212
+ adapterSource = credentialReferenceSource
213
+ ? resolvedSource.source
214
+ : driver === "sqlite" ? normalizeSqliteSource(secret) : secret;
215
+ }
216
+ const persistedSource = passwordRef !== undefined
217
+ ? resolvedTarget
218
+ : driver === "sqlite"
219
+ ? adapterSource
220
+ : credentialReferenceSource
221
+ ? redact(adapterSource)
222
+ : adapterSource;
223
+ const identitySource = passwordRef !== undefined ? resolvedTarget : adapterSource;
172
224
  const databaseName = driver === "sqlite"
173
225
  ? basename(adapterSource)
174
226
  : driver === "mongodb"
175
- ? mongoDatabaseName(adapterSource)
227
+ ? mongoDatabaseName(identitySource)
176
228
  : driver === "redis"
177
- ? redisDatabaseName(adapterSource)
178
- : new URL(secret).pathname.replace(/^\//, "") || driver;
229
+ ? redisDatabaseName(identitySource)
230
+ : new URL(identitySource).pathname.replace(/^\//, "") || driver;
179
231
  const draft = {
180
232
  id: "pending",
181
233
  session_id: session.id,
182
234
  name: options.name ?? profile?.name ?? databaseName,
183
235
  driver,
184
236
  database_name: databaseName,
185
- source,
237
+ source: persistedSource,
186
238
  secret_env: secretEnv ?? null,
187
239
  credential_ref: credentialRef ?? null,
240
+ password_ref: passwordRef ?? null,
188
241
  read_only: readOnly ? 1 : 0,
189
242
  version: 0,
190
243
  created_at: this.now().toISOString(),
@@ -212,9 +265,10 @@ export class StateQL {
212
265
  name: draft.name,
213
266
  driver,
214
267
  databaseName,
215
- source,
268
+ source: persistedSource,
216
269
  ...(secretEnv ? { secretEnv } : {}),
217
270
  ...(credentialRef ? { credentialRef } : {}),
271
+ ...(passwordRef !== undefined ? { passwordRef } : {}),
218
272
  readOnly,
219
273
  });
220
274
  if (!connection) {
@@ -250,12 +304,14 @@ export class StateQL {
250
304
  target,
251
305
  secretEnv: options.secretEnv,
252
306
  credentialRef: options.credentialRef,
307
+ passwordRef: options.passwordRef,
253
308
  });
254
309
  const profile = this.store.addProfile({
255
310
  name,
256
311
  target: source.target ?? undefined,
257
312
  secretEnv: source.secretEnv ?? undefined,
258
313
  credentialRef: source.credentialRef ?? undefined,
314
+ passwordRef: source.passwordRef ?? undefined,
259
315
  readOnly: options.readOnly ?? true,
260
316
  });
261
317
  return {
@@ -272,22 +328,32 @@ export class StateQL {
272
328
  if (!existing)
273
329
  throw new StateQLError("CONNECTION_NOT_FOUND", `Profile "${name}" was not found.`);
274
330
  if (!changes || typeof changes !== "object" || Array.isArray(changes) ||
275
- Object.keys(changes).some((key) => !["target", "secretEnv", "credentialRef", "readOnly"].includes(key))) {
331
+ Object.keys(changes).some((key) => !["target", "secretEnv", "credentialRef", "passwordRef", "readOnly"].includes(key))) {
276
332
  throw new StateQLError("INVALID_COMMAND", "Profile update contains unknown fields.");
277
333
  }
278
334
  if (changes.readOnly !== undefined && typeof changes.readOnly !== "boolean")
279
335
  throw new StateQLError("INVALID_COMMAND", "Profile readOnly must be boolean.");
280
336
  const changesSource = Object.hasOwn(changes, "target") || Object.hasOwn(changes, "secretEnv") || Object.hasOwn(changes, "credentialRef");
281
- if (!changesSource && changes.readOnly === undefined)
337
+ const changesPassword = Object.hasOwn(changes, "passwordRef");
338
+ if (!changesSource && !changesPassword && changes.readOnly === undefined)
282
339
  throw new StateQLError("INVALID_COMMAND", "Profile update has no changes.");
283
- const source = changesSource
284
- ? validatedProfileSource({ target: changes.target ?? undefined, secretEnv: changes.secretEnv ?? undefined, credentialRef: changes.credentialRef ?? undefined })
285
- : { target: existing.target, secretEnv: existing.secret_env, credentialRef: existing.credential_ref };
340
+ const targetSource = changesSource ? changes.target ?? undefined : existing.target ?? undefined;
341
+ const source = validatedProfileSource({
342
+ target: targetSource,
343
+ secretEnv: changesSource ? changes.secretEnv ?? undefined : existing.secret_env ?? undefined,
344
+ credentialRef: changesSource ? changes.credentialRef ?? undefined : existing.credential_ref ?? undefined,
345
+ passwordRef: changesPassword
346
+ ? changes.passwordRef ?? undefined
347
+ : !changesSource || targetSource === existing.target
348
+ ? existing.password_ref ?? undefined
349
+ : undefined,
350
+ });
286
351
  const profile = this.store.updateProfile({
287
352
  name,
288
353
  target: source.target,
289
354
  secretEnv: source.secretEnv,
290
355
  credentialRef: source.credentialRef,
356
+ passwordRef: source.passwordRef,
291
357
  readOnly: changes.readOnly ?? Boolean(existing.read_only),
292
358
  });
293
359
  if (!profile)
@@ -1711,7 +1777,7 @@ export class StateQL {
1711
1777
  const planParameters = compiled?.params ?? (nativePlan || tableUpdates
1712
1778
  ? undefined
1713
1779
  : parseJson(plan.parameters, `plan "${plan.id}" parameters`, isSqlParameters));
1714
- const claimToken = this.store.nextId("claim");
1780
+ const claimToken = this.store.randomId("claim");
1715
1781
  const claimed = this.store.claimPlan(plan.id, session.id, this.actorId, claimToken);
1716
1782
  if (!claimed) {
1717
1783
  throw new StateQLError("STALE_PLAN", "Plan is already being applied.");
@@ -1900,6 +1966,7 @@ export class StateQL {
1900
1966
  readOnly: command.read_only,
1901
1967
  secretEnv: command.secret_env,
1902
1968
  credentialRef: command.credential_ref,
1969
+ passwordRef: command.password_ref ?? undefined,
1903
1970
  profile: command.profile,
1904
1971
  timeoutMs: command.timeout_ms,
1905
1972
  });
@@ -1912,12 +1979,14 @@ export class StateQL {
1912
1979
  readOnly: command.read_only ?? true,
1913
1980
  secretEnv: command.secret_env,
1914
1981
  credentialRef: command.credential_ref,
1982
+ passwordRef: command.password_ref ?? undefined,
1915
1983
  });
1916
1984
  case "profile.update":
1917
1985
  return this.updateProfile(batchString(command.name, "name"), {
1918
1986
  ...(command.target !== undefined ? { target: command.target } : {}),
1919
1987
  ...(command.secret_env !== undefined ? { secretEnv: command.secret_env } : {}),
1920
1988
  ...(command.credential_ref !== undefined ? { credentialRef: command.credential_ref } : {}),
1989
+ ...(command.password_ref !== undefined ? { passwordRef: command.password_ref } : {}),
1921
1990
  ...(command.read_only !== undefined ? { readOnly: command.read_only } : {}),
1922
1991
  });
1923
1992
  case "profile.list":
@@ -2718,6 +2787,28 @@ export class StateQL {
2718
2787
  throw new StateQLError("PERMISSION_DENIED", `Actor "${this.actorId}" is not attached to session "${session.name}".`);
2719
2788
  }
2720
2789
  async resolveConnectionSource(connection, session, operation, access, context) {
2790
+ const references = [connection.secret_env, connection.credential_ref, connection.password_ref]
2791
+ .filter((value) => value !== null);
2792
+ if (references.length > 1) {
2793
+ throw new StateQLError("STATE_CORRUPTED", "Connection has ambiguous credential references.");
2794
+ }
2795
+ if (connection.password_ref !== null) {
2796
+ validateCredentialRef(connection.password_ref);
2797
+ const driver = validatePasswordReferenceTarget(connection.source);
2798
+ if (driver !== connection.driver) {
2799
+ throw new StateQLError("STATE_CORRUPTED", "Password-reference target driver does not match the stored connection.");
2800
+ }
2801
+ const password = await this.resolveCredential(connection.password_ref, "password_ref", session, operation, access, context, {
2802
+ connection: {
2803
+ id: connection.id,
2804
+ name: connection.name,
2805
+ driver: connection.driver,
2806
+ database: connection.database_name,
2807
+ readOnly: Boolean(connection.read_only),
2808
+ },
2809
+ }, connection.source);
2810
+ return injectPassword(connection.source, password).source;
2811
+ }
2721
2812
  const reference = connection.secret_env ?? connection.credential_ref;
2722
2813
  if (!reference)
2723
2814
  return connection.source;
@@ -2735,7 +2826,7 @@ export class StateQL {
2735
2826
  });
2736
2827
  return credentialSource(value, connection.driver, source).source;
2737
2828
  }
2738
- async resolveCredential(reference, source, session, operation, access, context, details = {}) {
2829
+ async resolveCredential(reference, source, session, operation, access, context, details = {}, passwordTarget) {
2739
2830
  const resolver = this.credentialResolver;
2740
2831
  const credentialContext = createAdapterContext(this.credentialTimeoutMs, context.signal);
2741
2832
  let value;
@@ -2750,9 +2841,8 @@ export class StateQL {
2750
2841
  value = env[reference];
2751
2842
  }
2752
2843
  else {
2753
- const request = {
2844
+ const baseRequest = {
2754
2845
  reference,
2755
- source,
2756
2846
  actorId: this.actorId,
2757
2847
  session: { id: session.id, name: session.name },
2758
2848
  operation,
@@ -2760,6 +2850,9 @@ export class StateQL {
2760
2850
  ...(context.signal ? { signal: context.signal } : {}),
2761
2851
  ...details,
2762
2852
  };
2853
+ const request = source === "password_ref"
2854
+ ? { ...baseRequest, source, target: passwordTarget }
2855
+ : { ...baseRequest, source };
2763
2856
  try {
2764
2857
  value = await resolveCredentialBeforeDeadline(resolver, request, credentialContext);
2765
2858
  }
@@ -2767,7 +2860,7 @@ export class StateQL {
2767
2860
  throw credentialStateQLError(reference, error);
2768
2861
  }
2769
2862
  }
2770
- if (!value) {
2863
+ if (value === undefined || (source !== "password_ref" && value === "")) {
2771
2864
  throw credentialStateQLError(reference, new CredentialResolutionError("unavailable"));
2772
2865
  }
2773
2866
  context.deadline = Date.now() + (context.timeoutMs ?? this.timeoutMs);
@@ -2844,8 +2937,8 @@ export class StateQL {
2844
2937
  const category = historyCategory(command);
2845
2938
  const internal = commandContext?.internal ?? false;
2846
2939
  let session = this.store.ensureSession(this.sessionName);
2847
- const commandId = this.store.nextId("cmd");
2848
2940
  if (!this.store.isSessionMember(session.id, this.actorId)) {
2941
+ const commandId = this.store.randomId("cmd");
2849
2942
  const error = new StateQLError("PERMISSION_DENIED", `Actor "${this.actorId}" is not attached to session "${session.name}".`);
2850
2943
  return {
2851
2944
  ok: false,
@@ -2861,8 +2954,7 @@ export class StateQL {
2861
2954
  const result = await action(session);
2862
2955
  const responseSession = result.session ?? session;
2863
2956
  const sqlText = resolveHistorySql(historySql);
2864
- this.store.addHistory({
2865
- id: commandId,
2957
+ const history = this.store.addHistory({
2866
2958
  sessionId: session.id,
2867
2959
  actorId: this.actorId,
2868
2960
  origin,
@@ -2878,7 +2970,7 @@ export class StateQL {
2878
2970
  });
2879
2971
  return {
2880
2972
  ok: true,
2881
- command_id: commandId,
2973
+ command_id: history.id,
2882
2974
  session_id: responseSession.id,
2883
2975
  data: result.data,
2884
2976
  warnings: result.warnings ?? [],
@@ -2896,8 +2988,7 @@ export class StateQL {
2896
2988
  catch (error) {
2897
2989
  const stateqlError = asStateQLError(error);
2898
2990
  const sqlText = resolveHistorySql(historySql);
2899
- this.store.addHistory({
2900
- id: commandId,
2991
+ const history = this.store.addHistory({
2901
2992
  sessionId: session.id,
2902
2993
  actorId: this.actorId,
2903
2994
  origin,
@@ -2913,7 +3004,7 @@ export class StateQL {
2913
3004
  });
2914
3005
  return {
2915
3006
  ok: false,
2916
- command_id: commandId,
3007
+ command_id: history.id,
2917
3008
  session_id: session.id,
2918
3009
  error: stateqlError.details,
2919
3010
  meta: {
@@ -3214,7 +3305,23 @@ function credentialStateQLError(reference, error) {
3214
3305
  return new StateQLError("CREDENTIAL_RESOLUTION_FAILED", `Credential reference "${reference}" could not be resolved.`, { retryable: true });
3215
3306
  }
3216
3307
  function safeCredentialErrorMessage(error, source) {
3217
- return redact(errorMessage(error).split(source).join("[credential redacted]"))
3308
+ let message = errorMessage(error).split(source).join("[credential redacted]");
3309
+ const authority = /^[a-z][a-z\d+.-]*:\/\/([^/?#]*)/i.exec(source)?.[1];
3310
+ if (authority) {
3311
+ const at = authority.lastIndexOf("@");
3312
+ const colon = at < 0 ? -1 : authority.slice(0, at).indexOf(":");
3313
+ const password = colon < 0 ? "" : authority.slice(colon + 1, at);
3314
+ const secrets = new Set([password]);
3315
+ try {
3316
+ secrets.add(decodeURIComponent(password));
3317
+ }
3318
+ catch { /* malformed values stay encoded */ }
3319
+ for (const secret of secrets) {
3320
+ if (secret)
3321
+ message = message.split(secret).join("[credential redacted]");
3322
+ }
3323
+ }
3324
+ return redact(message)
3218
3325
  .replace(/\b([a-z][a-z0-9+.-]*:\/\/)[^\s\/@]+(?::[^\s\/@]*)?@/giu, "$1***@");
3219
3326
  }
3220
3327
  function executionTimeout(value, name = "timeoutMs") {
@@ -3236,15 +3343,27 @@ function validatedProfileSource(input) {
3236
3343
  throw new StateQLError("INVALID_COMMAND", "Secret environment variable name is invalid.");
3237
3344
  if (input.credentialRef !== undefined)
3238
3345
  validateCredentialRef(input.credentialRef);
3346
+ if (input.passwordRef !== undefined)
3347
+ validateCredentialRef(input.passwordRef);
3239
3348
  let target = input.target ?? null;
3349
+ if (input.passwordRef !== undefined && target === null) {
3350
+ throw new StateQLError("INVALID_COMMAND", "A password reference requires a literal remote profile target.");
3351
+ }
3240
3352
  if (target) {
3241
- const driver = detectDriver(target);
3242
- if (driver !== "sqlite" && databaseUrlHasSecret(target))
3353
+ const driver = input.passwordRef !== undefined
3354
+ ? validatePasswordReferenceTarget(target)
3355
+ : detectDriver(target);
3356
+ if (input.passwordRef === undefined && driver !== "sqlite" && databaseUrlHasSecret(target))
3243
3357
  throw new StateQLError("PERMISSION_DENIED", `Credential-bearing ${databaseDisplayName(driver)} URLs must use --env or --credential-ref.`);
3244
3358
  if (driver === "sqlite")
3245
3359
  target = normalizeSqliteSource(target);
3246
3360
  }
3247
- return { target, secretEnv: input.secretEnv ?? null, credentialRef: input.credentialRef ?? null };
3361
+ return {
3362
+ target,
3363
+ secretEnv: input.secretEnv ?? null,
3364
+ credentialRef: input.credentialRef ?? null,
3365
+ passwordRef: input.passwordRef ?? null,
3366
+ };
3248
3367
  }
3249
3368
  function historyCategory(command) {
3250
3369
  if (["query", "exec", "plan", "apply", "mongo.query", "mongo.exec", "mongo.plan", "redis.query", "redis.exec", "redis.plan", "filter"].includes(command))