@fadhilp/stateql 0.4.2 → 0.4.4
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/README.md +227 -127
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,20 +1,28 @@
|
|
|
1
1
|
# StateQL
|
|
2
2
|
|
|
3
|
-
StateQL is a stateful database CLI for AI agents and
|
|
4
|
-
safe interface for querying, changing, and inspecting
|
|
5
|
-
|
|
3
|
+
StateQL is a stateful database CLI and TypeScript library for AI agents and
|
|
4
|
+
automation. It provides a safe interface for querying, changing, and inspecting
|
|
5
|
+
SQLite, PostgreSQL, and MySQL databases while keeping results reusable and
|
|
6
|
+
operations traceable across commands.
|
|
7
|
+
|
|
8
|
+
StateQL is built around durable handles:
|
|
9
|
+
|
|
10
|
+
1. Run a query and receive a result handle such as `q_1`.
|
|
11
|
+
2. Reuse, filter, page, count, alias, or export that stored result without
|
|
12
|
+
rerunning the original SQL.
|
|
13
|
+
3. Use operation, plan, and transaction handles to inspect and control writes.
|
|
6
14
|
|
|
7
15
|
Requires Node.js 22.5 or newer.
|
|
8
16
|
|
|
9
17
|
## Quick start
|
|
10
18
|
|
|
19
|
+
Install the CLI:
|
|
20
|
+
|
|
11
21
|
```bash
|
|
12
22
|
npm install -g @fadhilp/stateql
|
|
13
23
|
```
|
|
14
24
|
|
|
15
|
-
Connect to an existing SQLite database
|
|
16
|
-
query. Parameters keep values separate from SQL; `ORDER BY` makes paging
|
|
17
|
-
stable, while `LIMIT` bounds work at the database.
|
|
25
|
+
Connect to an existing SQLite database and run a bounded, parameterized query:
|
|
18
26
|
|
|
19
27
|
```bash
|
|
20
28
|
export STQL_SESSION=audit
|
|
@@ -27,14 +35,16 @@ stql query \
|
|
|
27
35
|
--param 2026-01-01
|
|
28
36
|
```
|
|
29
37
|
|
|
30
|
-
|
|
38
|
+
Parameters keep values separate from SQL. `ORDER BY` makes paging stable, and
|
|
39
|
+
`LIMIT` bounds work at the database. The default `agent` output is compact,
|
|
40
|
+
one-line JSON:
|
|
31
41
|
|
|
32
42
|
```json
|
|
33
43
|
{"ok":true,"handle":"q_1","rows":[{"id":7,"name":"Ada","email":"ada@example.com"},{"id":12,"name":"Grace","email":"grace@example.com"},{"id":18,"name":"Linus","email":"linus@kernel.org"}],"truncated":false,"cached":false,"total":3,"next_offset":null}
|
|
34
44
|
```
|
|
35
45
|
|
|
36
|
-
`q_1` is a durable
|
|
37
|
-
|
|
46
|
+
`q_1` is a durable snapshot. Filter it locally without accessing the original
|
|
47
|
+
database:
|
|
38
48
|
|
|
39
49
|
```bash
|
|
40
50
|
stql filter q_1 "email LIKE ?" --param "%@example.com"
|
|
@@ -44,8 +54,8 @@ stql filter q_1 "email LIKE ?" --param "%@example.com"
|
|
|
44
54
|
{"ok":true,"handle":"q_2","rows":[{"id":7,"name":"Ada","email":"ada@example.com"},{"id":12,"name":"Grace","email":"grace@example.com"}],"truncated":false,"cached":false,"total":2,"next_offset":null}
|
|
45
55
|
```
|
|
46
56
|
|
|
47
|
-
|
|
48
|
-
or export it
|
|
57
|
+
The filtered snapshot receives its own handle. Give it a readable alias, page
|
|
58
|
+
through it, inspect its count, or export it without rerunning SQL:
|
|
49
59
|
|
|
50
60
|
```bash
|
|
51
61
|
stql alias set example-users q_2
|
|
@@ -62,7 +72,22 @@ Example first page:
|
|
|
62
72
|
```
|
|
63
73
|
|
|
64
74
|
Running the same normalized query with the same parameters reuses `q_1` while
|
|
65
|
-
its cache is valid. Use `--cache bypass` when a fresh read is required.
|
|
75
|
+
its cache entry is valid. Use `--cache bypass` when a fresh read is required.
|
|
76
|
+
|
|
77
|
+
## Connections and profiles
|
|
78
|
+
|
|
79
|
+
A connection accepts exactly one source: a direct target, `--env`, or
|
|
80
|
+
`--profile`.
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
stql connect <sqlite-path|postgres-url|mysql-url> [--name NAME] [--read-write]
|
|
84
|
+
stql connect --env ENV [--name NAME] [--read-write]
|
|
85
|
+
stql connect --profile NAME
|
|
86
|
+
stql disconnect
|
|
87
|
+
stql status
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Environment-backed credentials
|
|
66
91
|
|
|
67
92
|
PostgreSQL and MySQL credentials should come from environment variables. The
|
|
68
93
|
variable must contain the complete connection URL, not only its password.
|
|
@@ -79,21 +104,45 @@ export SQLITE_DATABASE='sqlite:./app.sqlite'
|
|
|
79
104
|
stql connect --env SQLITE_DATABASE --name local --read-only
|
|
80
105
|
```
|
|
81
106
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
107
|
+
StateQL stores no PostgreSQL or MySQL password. Credential-bearing URLs must be
|
|
108
|
+
supplied through `--env`. SQLite paths remain persisted as connection metadata.
|
|
109
|
+
|
|
110
|
+
### Local profiles
|
|
111
|
+
|
|
112
|
+
Profiles store connection targets, read-only policy, and environment-variable
|
|
113
|
+
names. Credential values are never stored. Profiles persist under `STQL_HOME`
|
|
114
|
+
with other StateQL metadata.
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
stql profile add local ./app.sqlite --read-write
|
|
118
|
+
stql profile add production --env PROD_DATABASE_URL --read-only
|
|
119
|
+
stql profile list
|
|
120
|
+
stql profile show production
|
|
121
|
+
stql connect local
|
|
122
|
+
stql connect --profile production
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
A bare connection target matching a profile name resolves to that profile;
|
|
126
|
+
otherwise it remains a path or database URL.
|
|
127
|
+
|
|
128
|
+
### Driver notes
|
|
87
129
|
|
|
88
|
-
|
|
89
|
-
|
|
130
|
+
- **SQLite:** use a filesystem path for direct connections or `sqlite:` for an
|
|
131
|
+
environment-backed path.
|
|
132
|
+
- **PostgreSQL:** StateQL preserves strict TLS verification by normalizing
|
|
133
|
+
`sslmode=prefer`, `require`, and `verify-ca` to `verify-full` before opening
|
|
134
|
+
the adapter. Use `sslmode=verify-full` explicitly for clarity. Setting
|
|
135
|
+
`uselibpqcompat=true` opts out and keeps libpq-compatible SSL semantics.
|
|
136
|
+
- **MySQL:** uses positional `?` parameters. MariaDB compatibility is not
|
|
137
|
+
currently claimed.
|
|
90
138
|
|
|
91
|
-
##
|
|
139
|
+
## CLI reference
|
|
92
140
|
|
|
93
141
|
```text
|
|
94
142
|
stql connect <sqlite-path|postgres-url|mysql-url> [--name NAME] [--read-write]
|
|
95
143
|
stql connect --env ENV [--name NAME] [--read-write]
|
|
96
144
|
stql connect --profile NAME
|
|
145
|
+
stql disconnect
|
|
97
146
|
stql status
|
|
98
147
|
stql profile add|list|show|remove
|
|
99
148
|
stql session start|list|show|summary|close
|
|
@@ -103,6 +152,7 @@ stql exec <sql> [--params JSON | --param VALUE...] [--idempotency-key KEY] [--re
|
|
|
103
152
|
[--allow-unbounded] [--allow-destructive]
|
|
104
153
|
stql show|count|columns <result-handle>
|
|
105
154
|
stql rows <result-handle> [--offset N] [--limit N]
|
|
155
|
+
stql alias set <name> <result-handle>
|
|
106
156
|
stql export <result-handle> --output FILE [--format json|jsonl|csv]
|
|
107
157
|
stql inspect schema|table|columns|indexes|constraints [table]
|
|
108
158
|
stql transaction begin|status|commit|rollback [--isolation LEVEL]
|
|
@@ -115,56 +165,128 @@ stql batch [commands.json|commands.jsonl|-] [--continue-on-error]
|
|
|
115
165
|
stql pipe [--continue-on-error]
|
|
116
166
|
```
|
|
117
167
|
|
|
118
|
-
|
|
119
|
-
cancels active work. SQLite runs in a killable child process so long synchronous
|
|
120
|
-
statements cannot block StateQL's event loop. PostgreSQL uses server-side
|
|
121
|
-
`statement_timeout` plus client deadlines. MySQL deadlines destroy the active
|
|
122
|
-
connection. A timed-out write may return `OUTCOME_UNKNOWN` when commit status
|
|
123
|
-
cannot be proven.
|
|
168
|
+
### SQL parameters
|
|
124
169
|
|
|
125
|
-
|
|
170
|
+
For shell-safe positional parameters, repeat `--param`. JSON scalars become
|
|
171
|
+
their native types; other values remain strings.
|
|
126
172
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
173
|
+
```powershell
|
|
174
|
+
stql exec "INSERT INTO users (name, status) VALUES (?, ?)" `
|
|
175
|
+
--param Ada --param trial
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Use `--params JSON` for a JSON array or named parameters. Use
|
|
179
|
+
`--params-file FILE` when JSON is awkward to quote; `--params-file -` reads
|
|
180
|
+
JSON from standard input.
|
|
181
|
+
|
|
182
|
+
### Output modes
|
|
183
|
+
|
|
184
|
+
CLI output defaults to compact, one-line `agent` JSON. Successful responses
|
|
185
|
+
flatten useful data and expose the primary durable ID as `handle`. Errors retain
|
|
186
|
+
their complete error object. Empty warnings and tracing metadata are omitted.
|
|
130
187
|
|
|
131
188
|
```json
|
|
132
189
|
{"ok":false,"error":{"code":"UNBOUNDED_MUTATION","message":"Mutation has no WHERE clause.","retryable":false,"executed":false,"override_flag":"--allow-unbounded"}}
|
|
133
190
|
```
|
|
134
191
|
|
|
135
|
-
|
|
136
|
-
`--output jsonl` for that envelope on one line. `--output text` prints a short
|
|
137
|
-
human status; `--output silent` prints only a successful handle. Set
|
|
138
|
-
`STQL_OUTPUT` to choose a mode globally. For `export`, `--output` names the
|
|
139
|
-
file, so use `STQL_OUTPUT` to choose its response mode. Library responses keep
|
|
140
|
-
the full envelope regardless of CLI mode.
|
|
192
|
+
Other modes are:
|
|
141
193
|
|
|
142
|
-
|
|
143
|
-
|
|
194
|
+
- `--output json`: original pretty, verbose envelope.
|
|
195
|
+
- `--output jsonl`: verbose envelope on one line.
|
|
196
|
+
- `--output text`: short human-readable status.
|
|
197
|
+
- `--output silent`: only a successful handle.
|
|
144
198
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
199
|
+
Set `STQL_OUTPUT` to choose a mode globally. For `export`, `--output` names the
|
|
200
|
+
file, so use `STQL_OUTPUT` to choose the command's response mode. Library
|
|
201
|
+
responses always keep the full envelope.
|
|
202
|
+
|
|
203
|
+
### Deadlines and cancellation
|
|
204
|
+
|
|
205
|
+
Database commands accept `--timeout-ms N`; the default is 30,000 ms. `Ctrl+C`
|
|
206
|
+
cancels active work.
|
|
207
|
+
|
|
208
|
+
- SQLite runs in a killable child process so long synchronous statements cannot
|
|
209
|
+
block StateQL's event loop.
|
|
210
|
+
- PostgreSQL combines server-side `statement_timeout` with client deadlines.
|
|
211
|
+
- MySQL deadlines destroy the active connection.
|
|
212
|
+
|
|
213
|
+
A timed-out write may return `OUTCOME_UNKNOWN` when its commit status cannot be
|
|
214
|
+
proven.
|
|
215
|
+
|
|
216
|
+
## Durable state and result reuse
|
|
217
|
+
|
|
218
|
+
State metadata lives under `STQL_HOME`, or the platform data directory when
|
|
219
|
+
unset. StateQL keeps connections, sessions, handles, aliases, cache entries,
|
|
220
|
+
plans, transactions, history, and receipts available across CLI invocations.
|
|
221
|
+
|
|
222
|
+
### Sessions and actors
|
|
223
|
+
|
|
224
|
+
Set `STQL_SESSION` to select a named session and `STQL_ACTOR` to select an
|
|
225
|
+
attached actor. A session is a shared workspace: attached actors reuse its
|
|
226
|
+
connection, handles, aliases, cache, and state version. Plans and staged
|
|
227
|
+
transactions remain owned by the actor that created them.
|
|
228
|
+
|
|
229
|
+
Callers that omit `actor` keep the legacy behavior where the actor ID is the
|
|
230
|
+
session name.
|
|
149
231
|
|
|
150
|
-
|
|
151
|
-
quote. `--params-file -` reads JSON from standard input.
|
|
232
|
+
### Result lifetime and limits
|
|
152
233
|
|
|
153
|
-
|
|
234
|
+
SQLite result rows are materialized locally for durable access. Read cache
|
|
235
|
+
entries expire after five minutes, and materialized handles expire after 24
|
|
236
|
+
hours. Expired results and plans are deleted the next time StateQL opens.
|
|
154
237
|
|
|
155
|
-
|
|
238
|
+
Queries exceeding 10,000 rows or 16 MiB of serialized row data fail before
|
|
239
|
+
persistence. Narrow the `WHERE` clause, add `LIMIT`, or select fewer columns.
|
|
240
|
+
These caps bound persisted materialization; the independent deadline bounds
|
|
241
|
+
execution time.
|
|
242
|
+
|
|
243
|
+
Command history keeps the latest 10,000 entries per session. SQLite cache reuse
|
|
244
|
+
also checks the database file signature. PostgreSQL and MySQL cache reuse is
|
|
245
|
+
labeled `ttl_based` and is never authoritative.
|
|
246
|
+
|
|
247
|
+
### Local filtering
|
|
248
|
+
|
|
249
|
+
`filter` evaluates one scalar SQLite predicate against a stored result. It
|
|
250
|
+
preserves source order, state metadata, and expiry, and never accesses the
|
|
251
|
+
original database.
|
|
252
|
+
|
|
253
|
+
Use parameters for values. Subqueries, query-shaping clauses, and
|
|
254
|
+
non-allowlisted functions are rejected. Common deterministic functions such as
|
|
255
|
+
`lower`, `upper`, `length`, and `coalesce` are supported.
|
|
256
|
+
|
|
257
|
+
## Write safety
|
|
258
|
+
|
|
259
|
+
Destructive and unbounded operations require `--allow-destructive` and
|
|
260
|
+
`--allow-unbounded`, respectively. The flags are independent.
|
|
261
|
+
|
|
262
|
+
`plan` validates and stores a write for later application. A plan persists only
|
|
263
|
+
the flags explicitly supplied when it is created; `apply` never adds
|
|
264
|
+
authorization.
|
|
265
|
+
|
|
266
|
+
Use an idempotency key to protect retryable writes from duplicate execution:
|
|
156
267
|
|
|
157
268
|
```bash
|
|
158
|
-
stql
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
stql connect local
|
|
162
|
-
stql connect --profile production
|
|
269
|
+
stql exec "UPDATE jobs SET claimed = 1 WHERE id = ?" \
|
|
270
|
+
--param 42 \
|
|
271
|
+
--idempotency-key claim-job-42
|
|
163
272
|
```
|
|
164
273
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
274
|
+
If a write starts but StateQL cannot safely record its final outcome, it returns
|
|
275
|
+
`OUTCOME_UNKNOWN` and blocks automatic replay. Inspect database state before
|
|
276
|
+
using `--replay`. Interrupted commits remain fail-closed; stale `committing`
|
|
277
|
+
records become `outcome_unknown` after five minutes.
|
|
278
|
+
|
|
279
|
+
### Transactions
|
|
280
|
+
|
|
281
|
+
Transactions are staged in local state so they survive CLI invocations, then
|
|
282
|
+
executed atomically on commit. While a transaction is active, StateQL rejects
|
|
283
|
+
database reads, plans, connection changes, and disconnects. Commit or roll back
|
|
284
|
+
first.
|
|
285
|
+
|
|
286
|
+
SQLite supports `serializable`. PostgreSQL and MySQL also support
|
|
287
|
+
`repeatable read`, `read committed`, and `read uncommitted`. Server reads run
|
|
288
|
+
inside database-enforced read-only transactions. MySQL staged transactions
|
|
289
|
+
reject DDL because MySQL implicitly commits those statements.
|
|
168
290
|
|
|
169
291
|
## Batch and pipes
|
|
170
292
|
|
|
@@ -173,6 +295,8 @@ policy, and environment-variable names. Credential values are never stored.
|
|
|
173
295
|
the first error unless `--continue-on-error` is set. Output defaults to one
|
|
174
296
|
compact `agent` JSON object per line.
|
|
175
297
|
|
|
298
|
+
Pipe commands directly:
|
|
299
|
+
|
|
176
300
|
```bash
|
|
177
301
|
printf '%s\n' \
|
|
178
302
|
'{"command":"query","sql":"SELECT id, email FROM users ORDER BY id","as":"users"}' \
|
|
@@ -181,6 +305,8 @@ printf '%s\n' \
|
|
|
181
305
|
stql pipe
|
|
182
306
|
```
|
|
183
307
|
|
|
308
|
+
Or save a JSON array as `commands.json`:
|
|
309
|
+
|
|
184
310
|
```json
|
|
185
311
|
[
|
|
186
312
|
{
|
|
@@ -197,55 +323,21 @@ printf '%s\n' \
|
|
|
197
323
|
]
|
|
198
324
|
```
|
|
199
325
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
Batch filters use `where` for the predicate and may assign the derived result
|
|
204
|
-
with `as`. Database commands may set `timeout_ms`; otherwise they use the
|
|
205
|
-
30-second default.
|
|
206
|
-
|
|
207
|
-
State metadata lives under `STQL_HOME`, or the platform data directory when
|
|
208
|
-
unset. Set `STQL_SESSION` to select a named session and `STQL_ACTOR` to select
|
|
209
|
-
an attached actor for CLI invocations. A session is a shared workspace:
|
|
210
|
-
attached actors reuse its connection, handles, aliases, cache, and
|
|
211
|
-
state version, while plans and staged transactions remain owned by their
|
|
212
|
-
creating actor. Callers that omit `actor` keep the legacy behavior where the
|
|
213
|
-
actor ID is the session name.
|
|
214
|
-
|
|
215
|
-
Read cache entries expire after five minutes; materialized handles expire after
|
|
216
|
-
24 hours. Expired results and plans are deleted when StateQL next opens. Queries
|
|
217
|
-
exceeding 10,000 rows or 16 MiB of serialized row data fail before persistence;
|
|
218
|
-
add a narrower `WHERE` clause, `LIMIT`, or smaller column selection. These caps
|
|
219
|
-
bound persisted materialization, while the independent deadline bounds execution
|
|
220
|
-
time. Command history keeps the latest 10,000 entries per session. SQLite cache reuse also checks
|
|
221
|
-
the database file signature; PostgreSQL and MySQL reuse is labeled `ttl_based`,
|
|
222
|
-
never authoritative. Transactions are staged in local state so they survive CLI
|
|
223
|
-
invocations, then executed atomically on commit. Database reads, plans,
|
|
224
|
-
connection changes, and disconnects are rejected while a transaction is active;
|
|
225
|
-
commit or roll back first. SQLite supports `serializable`;
|
|
226
|
-
PostgreSQL and MySQL also support `repeatable read`, `read committed`, and
|
|
227
|
-
`read uncommitted`. Server reads run inside database-enforced read-only
|
|
228
|
-
transactions. MySQL staged transactions reject DDL because MySQL implicitly
|
|
229
|
-
commits those statements.
|
|
326
|
+
```bash
|
|
327
|
+
stql batch commands.json
|
|
328
|
+
```
|
|
230
329
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
`
|
|
235
|
-
|
|
236
|
-
database. Use parameters for values. Subqueries, query-shaping clauses, and
|
|
237
|
-
non-allowlisted functions are rejected; common deterministic functions such as
|
|
238
|
-
`lower`, `upper`, `length`, and `coalesce` are supported.
|
|
330
|
+
Batch fields use snake case. Supported command names match CLI paths, such as
|
|
331
|
+
`filter`, `transaction.begin`, `session.summary`, `alias.set`, `plan`, and
|
|
332
|
+
`apply`. Batch filters use `where` for the predicate and may assign the derived
|
|
333
|
+
result with `as`. Database commands may set `timeout_ms`; otherwise they use the
|
|
334
|
+
30-second default.
|
|
239
335
|
|
|
240
|
-
|
|
241
|
-
independently. Plans persist only flags explicitly supplied when the plan is
|
|
242
|
-
created; `apply` never adds authorization. If a database write starts but its
|
|
243
|
-
final outcome cannot be recorded safely, StateQL returns `OUTCOME_UNKNOWN` and
|
|
244
|
-
blocks automatic replay. Inspect database state before using `--replay`.
|
|
245
|
-
Interrupted commits remain fail-closed; stale `committing` records become
|
|
246
|
-
`outcome_unknown` after five minutes.
|
|
336
|
+
## TypeScript library
|
|
247
337
|
|
|
248
|
-
|
|
338
|
+
The package exports the same stateful operations for programmatic use. Library
|
|
339
|
+
responses retain the full response envelope regardless of the configured CLI
|
|
340
|
+
output mode.
|
|
249
341
|
|
|
250
342
|
```ts
|
|
251
343
|
import { StateQL } from "@fadhilp/stateql";
|
|
@@ -256,11 +348,13 @@ const stateql = StateQL.forActor({
|
|
|
256
348
|
timeoutMs: 30_000,
|
|
257
349
|
maxResultBytes: 16 * 1024 * 1024,
|
|
258
350
|
});
|
|
351
|
+
|
|
259
352
|
const controller = new AbortController();
|
|
260
353
|
const response = await stateql.query("SELECT * FROM users", {
|
|
261
354
|
signal: controller.signal,
|
|
262
355
|
timeoutMs: 5_000,
|
|
263
356
|
});
|
|
357
|
+
|
|
264
358
|
if (response.ok) {
|
|
265
359
|
const handle = (response.data as { result_id: string }).result_id;
|
|
266
360
|
await stateql.filter(handle, "email LIKE ?", {
|
|
@@ -269,6 +363,19 @@ if (response.ok) {
|
|
|
269
363
|
}
|
|
270
364
|
```
|
|
271
365
|
|
|
366
|
+
### Actor workspaces
|
|
367
|
+
|
|
368
|
+
`StateQL.forActor(...)` resolves the actor's attached session directly from
|
|
369
|
+
StateQL storage, avoiding a duplicate actor-to-session mapping in integrations.
|
|
370
|
+
On first use, it creates a legacy-compatible session named after the actor. Use
|
|
371
|
+
`new StateQL({ session, actor })` when the session is already known.
|
|
372
|
+
|
|
373
|
+
Membership is managed only through the library API, not batch commands:
|
|
374
|
+
`linkActor(session, actorId)`, `unlinkActor(session, actorId)`,
|
|
375
|
+
`listActors(session)`, and `resolveActor(actorId)`. An existing member must link
|
|
376
|
+
an actor before that actor opens an existing workspace. Integrations should ask
|
|
377
|
+
for user confirmation before changing membership or the shared connection.
|
|
378
|
+
|
|
272
379
|
### Harness credential resolution
|
|
273
380
|
|
|
274
381
|
Library integrations can resolve a profile's credential reference through a
|
|
@@ -292,6 +399,7 @@ async function resolveCredential(
|
|
|
292
399
|
access: request.access,
|
|
293
400
|
signal: request.signal,
|
|
294
401
|
});
|
|
402
|
+
|
|
295
403
|
if (approved.denied) throw new CredentialResolutionError("denied");
|
|
296
404
|
return approved.value;
|
|
297
405
|
}
|
|
@@ -302,39 +410,31 @@ const stateql = StateQL.forActor({
|
|
|
302
410
|
});
|
|
303
411
|
```
|
|
304
412
|
|
|
305
|
-
When no custom resolver is configured, StateQL
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
`
|
|
310
|
-
|
|
413
|
+
When no custom resolver is configured, StateQL reads references from
|
|
414
|
+
`process.env`. A configured resolver is authoritative: returning `undefined`
|
|
415
|
+
produces `CREDENTIAL_UNAVAILABLE` and never falls back to the process
|
|
416
|
+
environment. Resolvers may throw `CredentialResolutionError` with `denied`,
|
|
417
|
+
`cancelled`, `timeout`, or `unavailable` to produce controlled, secret-free
|
|
418
|
+
failures. Unknown resolver errors are replaced with a generic
|
|
311
419
|
`CREDENTIAL_RESOLUTION_FAILED` response.
|
|
312
420
|
|
|
313
421
|
StateQL calls the resolver only immediately before database access, after SQL
|
|
314
|
-
safety and duplicate checks. Requests contain actor
|
|
422
|
+
safety and duplicate checks. Requests contain actor and session identity, the
|
|
315
423
|
operation's effective read/write access, an abort signal, and sanitized
|
|
316
|
-
connection metadata.
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
424
|
+
connection metadata.
|
|
425
|
+
|
|
426
|
+
Returned values must be complete PostgreSQL or MySQL URLs, or explicit
|
|
427
|
+
`sqlite:` sources. StateQL validates the source and its stored driver before
|
|
428
|
+
adapter construction and normalizes SQLite paths. Credential-bearing
|
|
429
|
+
PostgreSQL and MySQL URLs are redacted before connection metadata is persisted
|
|
430
|
+
and never enter history, snapshots, cache keys, or responses. SQLite paths
|
|
431
|
+
remain persisted connection metadata, as they are for direct SQLite
|
|
432
|
+
connections.
|
|
433
|
+
|
|
434
|
+
Harnesses remain responsible for approval policy, binding lifetime, revocation,
|
|
435
|
+
and keeping values out of their own logs and model-visible data.
|
|
325
436
|
|
|
326
437
|
For writes, credential resolution happens after StateQL atomically reserves the
|
|
327
438
|
operation for duplicate protection. A resolution failure keeps a non-executed
|
|
328
439
|
`failed` audit record, does not consume the idempotency key, and permits a safe
|
|
329
440
|
retry.
|
|
330
|
-
|
|
331
|
-
`StateQL.forActor(...)` resolves the actor's attached session directly from
|
|
332
|
-
StateQL storage, avoiding a duplicate actor-to-session mapping in integrations.
|
|
333
|
-
On first use, it creates a legacy-compatible session named after the actor.
|
|
334
|
-
Use `new StateQL({ session, actor })` when the session is already known.
|
|
335
|
-
|
|
336
|
-
Membership is managed only through the library API, not batch commands:
|
|
337
|
-
`linkActor(session, actorId)`, `unlinkActor(session, actorId)`,
|
|
338
|
-
`listActors(session)`, and `resolveActor(actorId)`. An existing member must link
|
|
339
|
-
an actor before that actor opens an existing workspace. Integrations should ask
|
|
340
|
-
for user confirmation before changing membership or the shared connection.
|