@synapsor/runner 0.1.0-alpha.9 → 0.1.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.
- package/CHANGELOG.md +189 -0
- package/README.md +949 -164
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/runner.mjs +2982 -238
- package/docs/README.md +90 -15
- package/docs/app-owned-executors.md +38 -0
- package/docs/capability-authoring.md +265 -0
- package/docs/cloud-mode.md +24 -0
- package/docs/current-scope.md +29 -0
- package/docs/dependency-license-inventory.md +35 -0
- package/docs/doctor.md +98 -0
- package/docs/getting-started-own-database.md +131 -46
- package/docs/handler-helper.md +228 -0
- package/docs/http-mcp.md +85 -17
- package/docs/licensing.md +36 -0
- package/docs/local-mode.md +44 -25
- package/docs/mcp-audit.md +8 -8
- package/docs/mcp-client-setup.md +59 -21
- package/docs/openai-agents-sdk.md +57 -0
- package/docs/recipes.md +6 -6
- package/docs/release-notes.md +348 -0
- package/docs/release-policy.md +125 -0
- package/docs/result-envelope-v2.md +151 -0
- package/docs/rfcs/001-result-envelope-v2.md +143 -0
- package/docs/rfcs/002-app-owned-handler-helper.md +161 -0
- package/docs/rfcs/003-integrator-feedback-teardown.md +97 -0
- package/docs/store-lifecycle.md +83 -0
- package/docs/troubleshooting-first-run.md +6 -6
- package/docs/use-your-own-database.md +18 -0
- package/docs/writeback-executors.md +92 -1
- package/examples/app-owned-writeback/README.md +128 -0
- package/examples/app-owned-writeback/business-actions.md +221 -0
- package/examples/app-owned-writeback/command-handler.mjs +55 -0
- package/examples/app-owned-writeback/node-fastify-handler.mjs +64 -0
- package/examples/app-owned-writeback/python-fastapi-handler.py +66 -0
- package/examples/claude-desktop-postgres/Makefile +6 -0
- package/examples/claude-desktop-postgres/README.md +40 -0
- package/examples/cursor-postgres/Makefile +6 -0
- package/examples/cursor-postgres/README.md +30 -0
- package/examples/mcp-postgres-billing-app-handler/README.md +94 -0
- package/examples/mcp-postgres-billing-app-handler/app-handler.mjs +123 -0
- package/examples/mcp-postgres-billing-app-handler/docker-compose.yml +13 -0
- package/examples/mcp-postgres-billing-app-handler/schema.sql +59 -0
- package/examples/mcp-postgres-billing-app-handler/scripts/run-demo.sh +100 -0
- package/examples/mcp-postgres-billing-app-handler/seed.sql +39 -0
- package/examples/mcp-postgres-billing-app-handler/synapsor-handler.mjs +437 -0
- package/examples/mcp-postgres-billing-app-handler/synapsor.runner.json +158 -0
- package/examples/mysql-refund-agent/Makefile +4 -0
- package/examples/mysql-refund-agent/README.md +36 -0
- package/examples/openai-agents-http/Makefile +6 -0
- package/examples/openai-agents-http/README.md +33 -12
- package/examples/openai-agents-http/agent.py +29 -65
- package/examples/openai-agents-stdio/Makefile +6 -0
- package/examples/openai-agents-stdio/README.md +24 -6
- package/examples/openai-agents-stdio/agent.py +4 -2
- package/examples/raw-sql-vs-synapsor/Makefile +11 -0
- package/examples/raw-sql-vs-synapsor/README.md +41 -0
- package/examples/reference-support-billing-app/README.md +16 -16
- package/examples/reference-support-billing-app/mcp-client.generic.json +1 -1
- package/examples/support-billing-agent/Makefile +19 -0
- package/examples/support-billing-agent/README.md +89 -0
- package/examples/support-billing-agent/app/README.md +13 -0
- package/examples/support-billing-agent/db/schema.sql +91 -0
- package/examples/support-billing-agent/db/seed.sql +43 -0
- package/examples/support-billing-agent/docker-compose.yml +13 -0
- package/examples/support-billing-agent/scripts/run-demo.sh +15 -0
- package/examples/support-billing-agent/synapsor.runner.json +233 -0
- package/fixtures/benchmark/mcp-efficiency.json +53 -0
- package/fixtures/benchmark/mcp-efficiency.txt +25 -0
- package/fixtures/protocol/MANIFEST.json +54 -0
- package/fixtures/protocol/change-set.late-fee-waiver.v1.json +72 -0
- package/fixtures/protocol/execution-receipt.applied.v1.json +14 -0
- package/fixtures/protocol/execution-receipt.conflict.v1.json +15 -0
- package/fixtures/protocol/runner-registration.v1.json +22 -0
- package/fixtures/protocol/writeback-job.late-fee-waiver.v1.json +44 -0
- package/package.json +27 -4
- package/schemas/change-set.v1.schema.json +140 -0
- package/schemas/execution-receipt.v1.schema.json +34 -0
- package/schemas/onboarding-selection.v1.schema.json +132 -0
- package/schemas/runner-registration.v1.schema.json +48 -0
- package/schemas/synapsor.app-handler-receipt.v1.json +39 -0
- package/schemas/synapsor.app-handler-request.v1.json +119 -0
- package/schemas/synapsor.runner.schema.json +415 -0
- package/schemas/writeback-job.v1.schema.json +121 -0
|
@@ -14,11 +14,11 @@ tools from your selections.
|
|
|
14
14
|
|
|
15
15
|
## Fast path
|
|
16
16
|
|
|
17
|
-
Set one read-only database URL and run the
|
|
17
|
+
Set one read-only database URL and run the public guided path:
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
20
|
export DATABASE_URL="<postgres-or-mysql-read-url>"
|
|
21
|
-
|
|
21
|
+
npx -y -p @synapsor/runner synapsor-runner start --from-env DATABASE_URL
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
That command does the useful local mini-Synapsor path:
|
|
@@ -30,6 +30,8 @@ inspect your schema
|
|
|
30
30
|
-> optionally choose proposal/writeback rules
|
|
31
31
|
-> generate synapsor.runner.json
|
|
32
32
|
-> preview MCP tools exposed to the model
|
|
33
|
+
-> run a local smoke check of the tool boundary
|
|
34
|
+
-> optionally write .synapsor/smoke-input.json for one real row
|
|
33
35
|
-> print mcp serve and local UI commands
|
|
34
36
|
```
|
|
35
37
|
|
|
@@ -37,10 +39,23 @@ It does not print your database URL, put the URL in MCP client config, expose
|
|
|
37
39
|
`execute_sql`, expose approval/commit tools, or give the model write
|
|
38
40
|
credentials.
|
|
39
41
|
|
|
42
|
+
`start --from-env` is the shortest public command for first-run onboarding.
|
|
43
|
+
`onboard db --from-env DATABASE_URL` is the same explicit path if you prefer the
|
|
44
|
+
older command name in scripts.
|
|
45
|
+
|
|
46
|
+
During the wizard, provide the optional sample object id if you know one safe
|
|
47
|
+
row in the selected table. Runner writes `./.synapsor/smoke-input.json` with
|
|
48
|
+
that id and prints the exact `smoke call` command. If you skip it, use
|
|
49
|
+
`--json '{"<lookup_arg>":"<real_id>"}'` when you are ready to test one real
|
|
50
|
+
row.
|
|
51
|
+
|
|
40
52
|
The rest of this page shows the same flow step by step using the public
|
|
41
53
|
`synapsor-runner ...` CLI. From a source checkout, use `./bin/synapsor-runner ...` if the
|
|
42
54
|
global binary is not linked yet.
|
|
43
55
|
|
|
56
|
+
From a source checkout, `./scripts/use-your-db.sh` runs the same kind of
|
|
57
|
+
guided flow plus local repository checks.
|
|
58
|
+
|
|
44
59
|
## 1. Put the read URL in an environment variable
|
|
45
60
|
|
|
46
61
|
Do not pass connection strings on the command line.
|
|
@@ -81,7 +96,7 @@ other trusted database TLS setup.
|
|
|
81
96
|
## 2. Inspect metadata
|
|
82
97
|
|
|
83
98
|
```bash
|
|
84
|
-
npx -y -p @synapsor/runner
|
|
99
|
+
npx -y -p @synapsor/runner synapsor-runner inspect \
|
|
85
100
|
--engine auto \
|
|
86
101
|
--from-env DATABASE_URL \
|
|
87
102
|
--schema public
|
|
@@ -90,13 +105,13 @@ npx -y -p @synapsor/runner@alpha synapsor-runner inspect \
|
|
|
90
105
|
For a disposable staging URL, this also works:
|
|
91
106
|
|
|
92
107
|
```bash
|
|
93
|
-
npx -y -p @synapsor/runner
|
|
108
|
+
npx -y -p @synapsor/runner synapsor-runner inspect "$DATABASE_URL" --engine auto --schema public
|
|
94
109
|
```
|
|
95
110
|
|
|
96
111
|
For automation:
|
|
97
112
|
|
|
98
113
|
```bash
|
|
99
|
-
npx -y -p @synapsor/runner
|
|
114
|
+
npx -y -p @synapsor/runner synapsor-runner inspect \
|
|
100
115
|
--engine postgres \
|
|
101
116
|
--from-env DATABASE_URL \
|
|
102
117
|
--schema public \
|
|
@@ -113,9 +128,9 @@ to your staging table, primary key, tenant key, conflict column, visible fields,
|
|
|
113
128
|
allowed write fields, and business limits.
|
|
114
129
|
|
|
115
130
|
```bash
|
|
116
|
-
npx -y -p @synapsor/runner
|
|
117
|
-
npx -y -p @synapsor/runner
|
|
118
|
-
npx -y -p @synapsor/runner
|
|
131
|
+
npx -y -p @synapsor/runner synapsor-runner recipes list
|
|
132
|
+
npx -y -p @synapsor/runner synapsor-runner recipes show billing.late_fee_waiver
|
|
133
|
+
npx -y -p @synapsor/runner synapsor-runner recipes init billing.late_fee_waiver --output synapsor.runner.json
|
|
119
134
|
```
|
|
120
135
|
|
|
121
136
|
Use a recipe when the shape is close. Use the guided wizard or explicit flags
|
|
@@ -130,7 +145,7 @@ only the capabilities in your generated `synapsor.runner.json`.
|
|
|
130
145
|
In an interactive terminal, run the guided wizard:
|
|
131
146
|
|
|
132
147
|
```bash
|
|
133
|
-
npx -y -p @synapsor/runner
|
|
148
|
+
npx -y -p @synapsor/runner synapsor-runner init --from-env DATABASE_URL --mode read_only --wizard
|
|
134
149
|
```
|
|
135
150
|
|
|
136
151
|
The generated context and capabilities are based on your selections. Synapsor
|
|
@@ -143,7 +158,12 @@ trusted context -> capability -> MCP tool
|
|
|
143
158
|
|
|
144
159
|
You choose the source object, trusted tenant/principal bindings, namespace,
|
|
145
160
|
object name, lookup argument, visible fields, proposal fields, guards, and
|
|
146
|
-
approval role.
|
|
161
|
+
approval role. You can also provide an optional real object id so Runner writes
|
|
162
|
+
`./.synapsor/smoke-input.json` for the first local tool call. If the read URL
|
|
163
|
+
env var and trusted tenant/principal env vars are already set, the wizard also
|
|
164
|
+
attempts that smoke call immediately and stores the evidence/query audit in the
|
|
165
|
+
local ledger. If not, it prints the exact smoke command to run after you set
|
|
166
|
+
the env vars from `.env.example`.
|
|
147
167
|
|
|
148
168
|
Start with `read_only` to prove safe database reads first. Use `--mode review`
|
|
149
169
|
when you are ready to create proposal tools and guarded writeback setup.
|
|
@@ -157,7 +177,12 @@ The wizard:
|
|
|
157
177
|
- asks which tenant/scope column and backend session env vars are trusted;
|
|
158
178
|
- asks you to confirm primary key, conflict/version column, visible columns,
|
|
159
179
|
mode, semantic capability names, and proposal patch mappings;
|
|
160
|
-
-
|
|
180
|
+
- asks review-mode users to choose direct guarded SQL writeback, an app-owned
|
|
181
|
+
HTTP handler, or an app-owned command handler;
|
|
182
|
+
- previews the MCP tools and what is not exposed, then lets you revise visible
|
|
183
|
+
fields or capability names before writing files;
|
|
184
|
+
- attempts a first smoke call when you supplied a real object id and the
|
|
185
|
+
required trusted env vars are present;
|
|
161
186
|
- writes the generated config, `.env.example`, and MCP client snippets only
|
|
162
187
|
after final confirmation.
|
|
163
188
|
|
|
@@ -174,7 +199,7 @@ If you already know the reviewed table/action, generate config directly from
|
|
|
174
199
|
metadata and explicit flags:
|
|
175
200
|
|
|
176
201
|
```bash
|
|
177
|
-
npx -y -p @synapsor/runner
|
|
202
|
+
npx -y -p @synapsor/runner synapsor-runner init \
|
|
178
203
|
--from-env DATABASE_URL \
|
|
179
204
|
--engine postgres \
|
|
180
205
|
--schema public \
|
|
@@ -184,23 +209,54 @@ npx -y -p @synapsor/runner@alpha synapsor-runner init \
|
|
|
184
209
|
--mode review \
|
|
185
210
|
--visible-columns id,tenant_id,late_fee_cents,waiver_reason,updated_at \
|
|
186
211
|
--allowed-columns late_fee_cents,waiver_reason \
|
|
187
|
-
--
|
|
188
|
-
--
|
|
189
|
-
--
|
|
212
|
+
--tenant-column tenant_id \
|
|
213
|
+
--id-arg invoice_id \
|
|
214
|
+
--patch late_fee_cents=fixed:0,waiver_reason=arg:reason \
|
|
215
|
+
--patch-bounds late_fee_cents=0:5500 \
|
|
190
216
|
--write-url-env SYNAPSOR_DATABASE_WRITE_URL
|
|
191
217
|
```
|
|
192
218
|
|
|
219
|
+
If you omit `--namespace`, Runner derives the namespace from the table name
|
|
220
|
+
instead of defaulting to `source.*`. Add `--read-tool` and `--proposal-tool`
|
|
221
|
+
when you want exact capability names in the generated contract.
|
|
222
|
+
|
|
223
|
+
For app-owned writeback, replace the direct writer env with a handler executor:
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
npx -y -p @synapsor/runner synapsor-runner init \
|
|
227
|
+
--from-env DATABASE_URL \
|
|
228
|
+
--engine postgres \
|
|
229
|
+
--schema public \
|
|
230
|
+
--table invoices \
|
|
231
|
+
--namespace billing \
|
|
232
|
+
--object-name invoice \
|
|
233
|
+
--mode review \
|
|
234
|
+
--tenant-column tenant_id \
|
|
235
|
+
--id-arg invoice_id \
|
|
236
|
+
--patch late_fee_cents=fixed:0,waiver_reason=arg:reason \
|
|
237
|
+
--writeback http_handler \
|
|
238
|
+
--handler-url-env APP_WRITEBACK_URL \
|
|
239
|
+
--handler-token-env APP_WRITEBACK_TOKEN \
|
|
240
|
+
--emit-handler
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Handler-owned configs mark the Runner source as read-only unless you explicitly
|
|
244
|
+
pass `--write-url-env`, so `config validate` does not warn that direct SQL
|
|
245
|
+
writeback is disabled.
|
|
246
|
+
|
|
247
|
+
Use `--writeback command_handler --handler-command-env APP_WRITEBACK_COMMAND`
|
|
248
|
+
when your app-owned writer is a local command/script instead of HTTP.
|
|
249
|
+
|
|
193
250
|
Or generate from a saved inspection snapshot without reconnecting:
|
|
194
251
|
|
|
195
252
|
```bash
|
|
196
|
-
npx -y -p @synapsor/runner
|
|
253
|
+
npx -y -p @synapsor/runner synapsor-runner init \
|
|
197
254
|
--inspection-json schema-inspection.json \
|
|
198
255
|
--table invoices \
|
|
199
256
|
--namespace billing \
|
|
200
257
|
--object-name invoice \
|
|
201
258
|
--mode review \
|
|
202
|
-
--patch
|
|
203
|
-
--patch-from-arg waiver_reason=reason
|
|
259
|
+
--patch late_fee_cents=fixed:0,waiver_reason=arg:reason
|
|
204
260
|
```
|
|
205
261
|
|
|
206
262
|
The command uses inspected metadata for primary-key, tenant-key, conflict-column,
|
|
@@ -208,15 +264,15 @@ and default-visible-column suggestions. If a suggestion is ambiguous or missing,
|
|
|
208
264
|
pass explicit flags such as `--primary-key`, `--tenant-key`, and
|
|
209
265
|
`--conflict-column`.
|
|
210
266
|
|
|
211
|
-
Review mode requires at least one explicit `--patch
|
|
212
|
-
`--patch-from-arg`
|
|
213
|
-
tool.
|
|
267
|
+
Review mode requires at least one explicit `--patch` mapping, or the older
|
|
268
|
+
`--patch-fixed` / `--patch-from-arg` flags. Use `--mode read_only` if you only
|
|
269
|
+
want an inspect tool.
|
|
214
270
|
|
|
215
271
|
For bounded business actions, add reviewed value guards:
|
|
216
272
|
|
|
217
273
|
```bash
|
|
218
|
-
--
|
|
219
|
-
--
|
|
274
|
+
--patch-bounds credit_cents=0:10000
|
|
275
|
+
--status-guards status=open:pending_review
|
|
220
276
|
```
|
|
221
277
|
|
|
222
278
|
`--numeric-bound` keeps a proposed numeric column inside a fixed range before a
|
|
@@ -234,6 +290,7 @@ Create `onboarding-selection.json` from one table and one safe business action.
|
|
|
234
290
|
"version": 1,
|
|
235
291
|
"engine": "postgres",
|
|
236
292
|
"mode": "review",
|
|
293
|
+
"result_format": 2,
|
|
237
294
|
"read_url_env": "SYNAPSOR_DATABASE_READ_URL",
|
|
238
295
|
"write_url_env": "SYNAPSOR_DATABASE_WRITE_URL",
|
|
239
296
|
"schema": "public",
|
|
@@ -243,6 +300,8 @@ Create `onboarding-selection.json` from one table and one safe business action.
|
|
|
243
300
|
"conflict_column": "updated_at",
|
|
244
301
|
"namespace": "billing",
|
|
245
302
|
"object_name": "invoice",
|
|
303
|
+
"read_tool": "billing.inspect_invoice",
|
|
304
|
+
"proposal_tool": "billing.propose_late_fee_waiver",
|
|
246
305
|
"visible_columns": ["id", "tenant_id", "late_fee_cents", "waiver_reason", "updated_at"],
|
|
247
306
|
"allowed_columns": ["late_fee_cents", "waiver_reason"],
|
|
248
307
|
"patch": {
|
|
@@ -261,7 +320,7 @@ contain database URLs or passwords.
|
|
|
261
320
|
## 6. Generate runner files
|
|
262
321
|
|
|
263
322
|
```bash
|
|
264
|
-
npx -y -p @synapsor/runner
|
|
323
|
+
npx -y -p @synapsor/runner synapsor-runner init \
|
|
265
324
|
--spec onboarding-selection.json \
|
|
266
325
|
--non-interactive
|
|
267
326
|
```
|
|
@@ -281,20 +340,30 @@ files.
|
|
|
281
340
|
Generate or refresh MCP client snippets later with:
|
|
282
341
|
|
|
283
342
|
```bash
|
|
284
|
-
npx -y -p @synapsor/runner
|
|
285
|
-
npx -y -p @synapsor/runner
|
|
286
|
-
npx -y -p @synapsor/runner
|
|
287
|
-
npx -y -p @synapsor/runner
|
|
343
|
+
npx -y -p @synapsor/runner synapsor-runner mcp config generic --config ./synapsor.runner.json --store ./.synapsor/local.db
|
|
344
|
+
npx -y -p @synapsor/runner synapsor-runner mcp config claude-desktop --config ./synapsor.runner.json --store ./.synapsor/local.db
|
|
345
|
+
npx -y -p @synapsor/runner synapsor-runner mcp config cursor --config ./synapsor.runner.json --store ./.synapsor/local.db
|
|
346
|
+
npx -y -p @synapsor/runner synapsor-runner tools preview --config ./synapsor.runner.json --store ./.synapsor/local.db
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
Call one generated tool locally before wiring an MCP client:
|
|
350
|
+
|
|
351
|
+
```bash
|
|
352
|
+
npx -y -p @synapsor/runner synapsor-runner smoke call --config ./synapsor.runner.json --store ./.synapsor/local.db
|
|
288
353
|
```
|
|
289
354
|
|
|
355
|
+
`smoke call` uses the same runtime as the MCP server. It records evidence and
|
|
356
|
+
query audit for read tools, or creates a proposal for proposal tools, then
|
|
357
|
+
prints the commands to inspect evidence/proposals/replay from the local store.
|
|
358
|
+
|
|
290
359
|
The snippets contain the local command and args. They must not contain database
|
|
291
360
|
URLs, passwords, approval tools, commit tools, or write credentials.
|
|
292
361
|
|
|
293
362
|
## 7. Validate the config
|
|
294
363
|
|
|
295
364
|
```bash
|
|
296
|
-
npx -y -p @synapsor/runner
|
|
297
|
-
npx -y -p @synapsor/runner
|
|
365
|
+
npx -y -p @synapsor/runner synapsor-runner config validate --config synapsor.runner.json
|
|
366
|
+
npx -y -p @synapsor/runner synapsor-runner config show --config synapsor.runner.json --redacted
|
|
298
367
|
```
|
|
299
368
|
|
|
300
369
|
The config stores environment-variable names, not connection-string values.
|
|
@@ -302,7 +371,7 @@ The config stores environment-variable names, not connection-string values.
|
|
|
302
371
|
Run doctor after setting the referenced environment variables:
|
|
303
372
|
|
|
304
373
|
```bash
|
|
305
|
-
npx -y -p @synapsor/runner
|
|
374
|
+
npx -y -p @synapsor/runner synapsor-runner doctor --config synapsor.runner.json
|
|
306
375
|
```
|
|
307
376
|
|
|
308
377
|
Doctor validates config shape, trusted context env vars, source env vars,
|
|
@@ -310,7 +379,7 @@ read/write credential separation, table/column metadata when the read URL is
|
|
|
310
379
|
available, and the semantic MCP tool boundary. Use JSON for automation:
|
|
311
380
|
|
|
312
381
|
```bash
|
|
313
|
-
npx -y -p @synapsor/runner
|
|
382
|
+
npx -y -p @synapsor/runner synapsor-runner doctor --config synapsor.runner.json --json
|
|
314
383
|
```
|
|
315
384
|
|
|
316
385
|
## 8. Serve semantic MCP tools
|
|
@@ -320,25 +389,41 @@ Use stdio when a local MCP client can launch Synapsor Runner:
|
|
|
320
389
|
```bash
|
|
321
390
|
export SYNAPSOR_TENANT_ID="acme"
|
|
322
391
|
export SYNAPSOR_PRINCIPAL="local_operator"
|
|
323
|
-
npx -y -p @synapsor/runner
|
|
392
|
+
npx -y -p @synapsor/runner synapsor-runner mcp serve --config ./synapsor.runner.json --store ./.synapsor/local.db
|
|
324
393
|
```
|
|
325
394
|
|
|
326
|
-
Use HTTP when an app/server agent connects
|
|
395
|
+
Use Streamable HTTP when an app/server agent connects through a standard HTTP
|
|
396
|
+
MCP client:
|
|
327
397
|
|
|
328
398
|
```bash
|
|
329
399
|
export SYNAPSOR_TENANT_ID="acme"
|
|
330
400
|
export SYNAPSOR_PRINCIPAL="local_operator"
|
|
331
401
|
export SYNAPSOR_RUNNER_HTTP_TOKEN="dev-local-token"
|
|
332
402
|
|
|
333
|
-
npx -y -p @synapsor/runner
|
|
403
|
+
npx -y -p @synapsor/runner synapsor-runner up --serve \
|
|
404
|
+
--config ./synapsor.runner.json \
|
|
405
|
+
--store ./.synapsor/local.db \
|
|
406
|
+
--auth-token-env SYNAPSOR_RUNNER_HTTP_TOKEN
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
`up --serve` runs the review-mode checklist first, then starts Streamable HTTP.
|
|
410
|
+
Use `--dry-run` for the checklist only, or `--with-handler` when the config uses
|
|
411
|
+
an app-owned executor and you want Runner to check the handler endpoint before
|
|
412
|
+
serving.
|
|
413
|
+
|
|
414
|
+
The lower-level MCP command starts the same transport directly:
|
|
415
|
+
|
|
416
|
+
```bash
|
|
417
|
+
npx -y -p @synapsor/runner synapsor-runner mcp serve-streamable-http \
|
|
334
418
|
--config ./synapsor.runner.json \
|
|
335
419
|
--store ./.synapsor/local.db \
|
|
336
420
|
--auth-token-env SYNAPSOR_RUNNER_HTTP_TOKEN
|
|
337
421
|
```
|
|
338
422
|
|
|
339
|
-
HTTP defaults to `127.0.0.1:
|
|
340
|
-
private networking/TLS before exposing it beyond localhost. See
|
|
341
|
-
[HTTP MCP](http-mcp.md).
|
|
423
|
+
Streamable HTTP defaults to `127.0.0.1:8766` and requires bearer auth by
|
|
424
|
+
default. Use private networking/TLS before exposing it beyond localhost. See
|
|
425
|
+
[HTTP MCP](http-mcp.md). If you want the smaller JSON-RPC bridge instead, use
|
|
426
|
+
`synapsor-runner mcp serve-http`.
|
|
342
427
|
|
|
343
428
|
The model-facing MCP server exposes semantic tools such as:
|
|
344
429
|
|
|
@@ -357,10 +442,10 @@ tools, commit tools, database URLs, write credentials, or tenant authority.
|
|
|
357
442
|
Proposal tools leave the source database unchanged. Review locally:
|
|
358
443
|
|
|
359
444
|
```bash
|
|
360
|
-
npx -y -p @synapsor/runner
|
|
361
|
-
npx -y -p @synapsor/runner
|
|
362
|
-
npx -y -p @synapsor/runner
|
|
363
|
-
npx -y -p @synapsor/runner
|
|
445
|
+
npx -y -p @synapsor/runner synapsor-runner proposals list --store ./.synapsor/local.db
|
|
446
|
+
npx -y -p @synapsor/runner synapsor-runner proposals show wrp_123 --store ./.synapsor/local.db
|
|
447
|
+
npx -y -p @synapsor/runner synapsor-runner proposals approve wrp_123 --store ./.synapsor/local.db --actor local_reviewer --yes
|
|
448
|
+
npx -y -p @synapsor/runner synapsor-runner proposals writeback-job wrp_123 --store ./.synapsor/local.db --output job.json
|
|
364
449
|
```
|
|
365
450
|
|
|
366
451
|
Apply through the trusted worker path with a separate writer credential:
|
|
@@ -368,7 +453,7 @@ Apply through the trusted worker path with a separate writer credential:
|
|
|
368
453
|
```bash
|
|
369
454
|
export SYNAPSOR_DATABASE_WRITE_URL="<postgres-or-mysql-write-url>"
|
|
370
455
|
SYNAPSOR_ENGINE=postgres \
|
|
371
|
-
npx -y -p @synapsor/runner
|
|
456
|
+
npx -y -p @synapsor/runner synapsor-runner apply --job job.json --config synapsor.runner.json --store ./.synapsor/local.db
|
|
372
457
|
```
|
|
373
458
|
|
|
374
459
|
For `apply --job ... --config ...`, Runner reads the write credential from the
|
|
@@ -382,7 +467,7 @@ from environment variables, and the handler receives a structured proposal/job
|
|
|
382
467
|
payload, not arbitrary model SQL:
|
|
383
468
|
|
|
384
469
|
```bash
|
|
385
|
-
npx -y -p @synapsor/runner
|
|
470
|
+
npx -y -p @synapsor/runner synapsor-runner apply --proposal wrp_123 --config synapsor.runner.json --store ./.synapsor/local.db
|
|
386
471
|
```
|
|
387
472
|
|
|
388
473
|
See [Writeback Executors](writeback-executors.md).
|
|
@@ -390,8 +475,8 @@ See [Writeback Executors](writeback-executors.md).
|
|
|
390
475
|
Replay afterward:
|
|
391
476
|
|
|
392
477
|
```bash
|
|
393
|
-
npx -y -p @synapsor/runner
|
|
394
|
-
npx -y -p @synapsor/runner
|
|
478
|
+
npx -y -p @synapsor/runner synapsor-runner replay show wrp_123 --store ./.synapsor/local.db
|
|
479
|
+
npx -y -p @synapsor/runner synapsor-runner replay export wrp_123 --store ./.synapsor/local.db --output replay.json
|
|
395
480
|
```
|
|
396
481
|
|
|
397
482
|
## Boundary
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# App-Owned Handler Helper
|
|
2
|
+
|
|
3
|
+
Use the TypeScript handler helper when an approved Synapsor proposal should be
|
|
4
|
+
executed by your application service, not by Runner's direct SQL writer.
|
|
5
|
+
|
|
6
|
+
This is optional support code for your app-owned handler. Users install
|
|
7
|
+
`@synapsor/runner`; they do not install a separate handler package in the
|
|
8
|
+
current alpha.
|
|
9
|
+
|
|
10
|
+
The helper is the safe-by-default path for rich writes such as:
|
|
11
|
+
|
|
12
|
+
- inserting account-credit, refund-review, ticket, or ledger rows;
|
|
13
|
+
- updating multiple related rows inside your app transaction;
|
|
14
|
+
- applying business rules that belong in your application service.
|
|
15
|
+
|
|
16
|
+
The model-facing MCP tool still creates a proposal only. A human/operator
|
|
17
|
+
approves outside MCP. After approval, Runner sends the structured writeback
|
|
18
|
+
request to your handler.
|
|
19
|
+
|
|
20
|
+
> **Important:** your app handler owns the final business write. Runner creates
|
|
21
|
+
> the proposal and calls your handler only after approval, but your handler must
|
|
22
|
+
> still enforce tenant/scope checks, expected-version or conflict guards,
|
|
23
|
+
> idempotency keys, allowed business actions, transaction/rollback, and safe
|
|
24
|
+
> error receipts. If you skip those checks, you can reintroduce cross-tenant
|
|
25
|
+
> writes, lost updates, or duplicate writes. Keep handler credentials out of MCP.
|
|
26
|
+
|
|
27
|
+
## Scope
|
|
28
|
+
|
|
29
|
+
Current alpha scope:
|
|
30
|
+
|
|
31
|
+
- TypeScript helper in `packages/handler`;
|
|
32
|
+
- bearer token verification;
|
|
33
|
+
- optional HMAC verification over the raw request body;
|
|
34
|
+
- typed request parsing;
|
|
35
|
+
- action dispatch;
|
|
36
|
+
- idempotency receipt lookup;
|
|
37
|
+
- transaction wrapper;
|
|
38
|
+
- `SELECT ... FOR UPDATE` target-row lock;
|
|
39
|
+
- tenant guard;
|
|
40
|
+
- expected-version stale-row guard;
|
|
41
|
+
- safe applied/conflict/failed receipts;
|
|
42
|
+
- no raw driver errors in HTTP responses.
|
|
43
|
+
|
|
44
|
+
Python helper is planned. For now, Python handlers should follow the documented
|
|
45
|
+
request/receipt schema and the FastAPI template in `examples/app-owned-writeback`.
|
|
46
|
+
|
|
47
|
+
## Distribution Status
|
|
48
|
+
|
|
49
|
+
The helper implementation exists in this source repo under `packages/handler`
|
|
50
|
+
and is used by the app-owned executor example and tests. It is not published as
|
|
51
|
+
a standalone npm package yet.
|
|
52
|
+
|
|
53
|
+
If you installed `@synapsor/runner` from npm, use one of these alpha paths:
|
|
54
|
+
|
|
55
|
+
- generate a starter handler with `synapsor-runner handler template ...`;
|
|
56
|
+
- copy from `examples/app-owned-writeback/`;
|
|
57
|
+
- run `examples/mcp-postgres-billing-app-handler/`, which includes a bundled
|
|
58
|
+
`synapsor-handler.mjs` shim inside the runner package.
|
|
59
|
+
|
|
60
|
+
## Schemas
|
|
61
|
+
|
|
62
|
+
Published schemas:
|
|
63
|
+
|
|
64
|
+
- `schemas/synapsor.app-handler-request.v1.json`
|
|
65
|
+
- `schemas/synapsor.app-handler-receipt.v1.json`
|
|
66
|
+
|
|
67
|
+
The helper accepts both the new `protocol_version: "1.0"` shape and the current
|
|
68
|
+
Runner `schema_version: "synapsor.handler-writeback.v1"` request shape during
|
|
69
|
+
the alpha migration.
|
|
70
|
+
|
|
71
|
+
## TypeScript Usage From A Source Checkout
|
|
72
|
+
|
|
73
|
+
The source checkout has an internal helper under `packages/handler`. It is not
|
|
74
|
+
an npm install path. Import it by workspace-relative path while developing this
|
|
75
|
+
repository, or use the bundled example shim in the packaged runner.
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
import { createWritebackHandler } from "../packages/handler/src/index.js";
|
|
79
|
+
|
|
80
|
+
export const handler = createWritebackHandler({
|
|
81
|
+
tokenEnv: "SYNAPSOR_APP_HANDLER_TOKEN",
|
|
82
|
+
signingSecretEnv: "SYNAPSOR_APP_HANDLER_SIGNING_SECRET",
|
|
83
|
+
source: {
|
|
84
|
+
engine: "postgres",
|
|
85
|
+
writeUrlEnv: "SYNAPSOR_APP_WRITE_URL",
|
|
86
|
+
receiptTable: { schema: "synapsor", table: "handler_receipts" }
|
|
87
|
+
},
|
|
88
|
+
capabilities: {
|
|
89
|
+
"support.propose_plan_credit": async (job, tx) => {
|
|
90
|
+
const creditId = `CR-${job.proposalId.slice(-12)}`;
|
|
91
|
+
|
|
92
|
+
await tx.insert("credits", {
|
|
93
|
+
id: creditId,
|
|
94
|
+
tenant_id: job.tenantId,
|
|
95
|
+
invoice_id: job.objectId,
|
|
96
|
+
amount_cents: Number(job.patch.credit_requested_cents),
|
|
97
|
+
reason: String(job.patch.credit_reason),
|
|
98
|
+
created_by: job.principal
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
await tx.update("invoices", {
|
|
102
|
+
id: job.objectId,
|
|
103
|
+
tenant_id: job.tenantId
|
|
104
|
+
}, {
|
|
105
|
+
credited_cents:
|
|
106
|
+
Number(job.row.credited_cents ?? 0) +
|
|
107
|
+
Number(job.patch.credit_requested_cents)
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
rowsAffected: 2,
|
|
112
|
+
effects: [{ type: "db.insert", table: "credits", id: creditId }]
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Mount the returned handler at your app route, for example
|
|
120
|
+
`POST /synapsor/writeback`.
|
|
121
|
+
|
|
122
|
+
The handler author writes only the business effect. The helper owns the safety
|
|
123
|
+
loop around that effect.
|
|
124
|
+
|
|
125
|
+
## What The Helper Enforces
|
|
126
|
+
|
|
127
|
+
The helper checks these before your business function can mutate state:
|
|
128
|
+
|
|
129
|
+
- the bearer token matches the configured environment variable;
|
|
130
|
+
- the optional HMAC signature is valid and fresh;
|
|
131
|
+
- the request protocol is supported;
|
|
132
|
+
- the action maps to a configured capability function;
|
|
133
|
+
- the target row exists inside the trusted tenant;
|
|
134
|
+
- the row version still matches the proposal's expected version;
|
|
135
|
+
- the idempotency key was not already applied.
|
|
136
|
+
|
|
137
|
+
If the row is missing or belongs to another tenant, the helper returns:
|
|
138
|
+
|
|
139
|
+
```json
|
|
140
|
+
{
|
|
141
|
+
"status": "conflict",
|
|
142
|
+
"rows_affected": 0,
|
|
143
|
+
"source_database_mutated": false,
|
|
144
|
+
"safe_error_code": "ROW_NOT_FOUND_OR_WRONG_TENANT"
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
If the row changed after proposal creation, the helper returns:
|
|
149
|
+
|
|
150
|
+
```json
|
|
151
|
+
{
|
|
152
|
+
"status": "conflict",
|
|
153
|
+
"rows_affected": 0,
|
|
154
|
+
"source_database_mutated": false,
|
|
155
|
+
"safe_error_code": "ROW_CHANGED_AFTER_PROPOSAL"
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
If your business function throws, the helper rolls back the transaction and
|
|
160
|
+
returns a safe failed receipt. Raw driver and exception text are not exposed to
|
|
161
|
+
the caller.
|
|
162
|
+
|
|
163
|
+
## Runner-Side Signing Config
|
|
164
|
+
|
|
165
|
+
Configure the matching `http_handler` executor with the same signing-secret env
|
|
166
|
+
name:
|
|
167
|
+
|
|
168
|
+
```json
|
|
169
|
+
{
|
|
170
|
+
"executors": {
|
|
171
|
+
"billing_handler": {
|
|
172
|
+
"type": "http_handler",
|
|
173
|
+
"url_env": "BILLING_WRITEBACK_URL",
|
|
174
|
+
"method": "POST",
|
|
175
|
+
"auth": {
|
|
176
|
+
"type": "bearer_env",
|
|
177
|
+
"token_env": "BILLING_WRITEBACK_TOKEN"
|
|
178
|
+
},
|
|
179
|
+
"signing_secret_env": "SYNAPSOR_APP_HANDLER_SIGNING_SECRET",
|
|
180
|
+
"timeout_ms": 5000
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
When this field is set, Runner signs the exact request body and sends
|
|
187
|
+
`X-Synapsor-Signature`, `X-Synapsor-Issued-At`,
|
|
188
|
+
`X-Synapsor-Proposal-Id`, and `Idempotency-Key`. The helper verifies those
|
|
189
|
+
headers before parsing or applying the writeback request.
|
|
190
|
+
|
|
191
|
+
## Signing
|
|
192
|
+
|
|
193
|
+
For loopback-only development, bearer auth may be enough. For any handler that
|
|
194
|
+
is reachable outside the local process, enable HMAC:
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
createWritebackHandler({
|
|
198
|
+
tokenEnv: "SYNAPSOR_APP_HANDLER_TOKEN",
|
|
199
|
+
signingSecretEnv: "SYNAPSOR_APP_HANDLER_SIGNING_SECRET",
|
|
200
|
+
// ...
|
|
201
|
+
});
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Runner sends:
|
|
205
|
+
|
|
206
|
+
```text
|
|
207
|
+
Authorization: Bearer <token>
|
|
208
|
+
X-Synapsor-Signature: sha256=<hmac>
|
|
209
|
+
X-Synapsor-Issued-At: <iso timestamp>
|
|
210
|
+
X-Synapsor-Proposal-Id: wrp_...
|
|
211
|
+
Idempotency-Key: wrp_...
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
The HMAC is computed over the raw body. The helper enforces a short issued-at
|
|
215
|
+
skew window.
|
|
216
|
+
|
|
217
|
+
## Receipt Storage
|
|
218
|
+
|
|
219
|
+
The helper's Postgres adapter stores idempotency receipts in a receipt table.
|
|
220
|
+
Prefer a dedicated schema, for example:
|
|
221
|
+
|
|
222
|
+
```sql
|
|
223
|
+
CREATE SCHEMA IF NOT EXISTS synapsor;
|
|
224
|
+
GRANT USAGE, CREATE ON SCHEMA synapsor TO app_writeback_user;
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
If your application already has a receipt/idempotency table, implement the
|
|
228
|
+
`WritebackHandlerDatabase` interface and pass it as `database`.
|