@synapsor/runner 0.1.0-alpha.1 → 0.1.0-alpha.10
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 +387 -19
- package/TRADEMARKS.md +23 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +20 -8723
- package/dist/runner.mjs +12759 -0
- package/docs/README.md +36 -0
- package/docs/getting-started-own-database.md +460 -0
- package/docs/http-mcp.md +242 -0
- package/docs/limitations.md +95 -0
- package/docs/local-mode.md +351 -0
- package/docs/mcp-audit.md +152 -0
- package/docs/mcp-client-setup.md +231 -0
- package/docs/recipes.md +61 -0
- package/docs/release-notes.md +129 -0
- package/docs/security-boundary.md +94 -0
- package/docs/troubleshooting-first-run.md +248 -0
- package/docs/writeback-executors.md +209 -0
- package/examples/app-owned-writeback/README.md +120 -0
- package/examples/app-owned-writeback/business-actions.md +221 -0
- package/examples/app-owned-writeback/command-handler.mjs +46 -0
- package/examples/app-owned-writeback/node-fastify-handler.mjs +55 -0
- package/examples/app-owned-writeback/python-fastapi-handler.py +57 -0
- package/examples/dangerous-mcp-tools.json +88 -0
- package/examples/openai-agents-http/README.md +56 -0
- package/examples/openai-agents-http/agent.py +54 -0
- package/examples/openai-agents-http/requirements.txt +1 -0
- package/examples/openai-agents-stdio/README.md +62 -0
- package/examples/openai-agents-stdio/agent.py +70 -0
- package/examples/openai-agents-stdio/requirements.txt +1 -0
- package/examples/reference-support-billing-app/README.md +137 -0
- package/examples/reference-support-billing-app/docker-compose.yml +13 -0
- package/examples/reference-support-billing-app/mcp-client.generic.json +11 -0
- package/examples/reference-support-billing-app/schema.sql +68 -0
- package/examples/reference-support-billing-app/scripts/run-demo.sh +7 -0
- package/examples/reference-support-billing-app/seed.sql +33 -0
- package/examples/reference-support-billing-app/synapsor.runner.json +241 -0
- package/package.json +12 -4
- package/recipes/accounts.trial_extension.json +42 -0
- package/recipes/billing.late_fee_waiver.json +46 -0
- package/recipes/credits.account_credit.json +45 -0
- package/recipes/orders.refund_review.json +57 -0
- package/recipes/support.ticket_resolution.json +51 -0
- package/dist/bin.cjs +0 -13
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
# Troubleshooting First Run
|
|
2
|
+
|
|
3
|
+
Run the friendly doctor first:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx -y -p @synapsor/runner@alpha synapsor-runner doctor --first-run
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Use JSON for automation:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx -y -p @synapsor/runner@alpha synapsor-runner doctor --first-run --json
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Docker Missing
|
|
16
|
+
|
|
17
|
+
What happened:
|
|
18
|
+
|
|
19
|
+
```text
|
|
20
|
+
Docker CLI is missing.
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Why it matters:
|
|
24
|
+
|
|
25
|
+
The first-run demo starts disposable Postgres/MySQL containers.
|
|
26
|
+
|
|
27
|
+
Fix:
|
|
28
|
+
|
|
29
|
+
Install Docker Desktop or Docker Engine, then rerun:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
./scripts/try-synapsor.sh
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Docker Daemon Stopped
|
|
36
|
+
|
|
37
|
+
What happened:
|
|
38
|
+
|
|
39
|
+
```text
|
|
40
|
+
Docker daemon is not reachable.
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Why it matters:
|
|
44
|
+
|
|
45
|
+
The demo cannot start disposable databases without the daemon.
|
|
46
|
+
|
|
47
|
+
Fix:
|
|
48
|
+
|
|
49
|
+
Start Docker Desktop or the Docker service, then rerun:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
./scripts/try-synapsor.sh
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
If the doctor reports Docker socket permission problems, add your user to the
|
|
56
|
+
Docker group or start Docker Desktop.
|
|
57
|
+
|
|
58
|
+
## Port Conflict
|
|
59
|
+
|
|
60
|
+
What happened:
|
|
61
|
+
|
|
62
|
+
```text
|
|
63
|
+
Port 55433 is already in use.
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Why it matters:
|
|
67
|
+
|
|
68
|
+
The fixtures bind predictable local ports.
|
|
69
|
+
|
|
70
|
+
Fix:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
./scripts/try-synapsor.sh --reset
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
If another application owns the port, stop that application and rerun.
|
|
77
|
+
|
|
78
|
+
## Stale Containers
|
|
79
|
+
|
|
80
|
+
What happened:
|
|
81
|
+
|
|
82
|
+
Doctor reports stale Synapsor demo containers.
|
|
83
|
+
|
|
84
|
+
Why it matters:
|
|
85
|
+
|
|
86
|
+
Old containers can hold ports or stale fixture state.
|
|
87
|
+
|
|
88
|
+
Fix:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
./scripts/try-synapsor.sh --reset
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Missing Source Dependencies
|
|
95
|
+
|
|
96
|
+
What happened:
|
|
97
|
+
|
|
98
|
+
```text
|
|
99
|
+
Dependencies are not installed yet.
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Why it matters:
|
|
103
|
+
|
|
104
|
+
Source checkout commands such as `synapsor ...` need workspace
|
|
105
|
+
dependencies.
|
|
106
|
+
|
|
107
|
+
Fix:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
corepack enable
|
|
111
|
+
corepack pnpm install
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
The Docker-only first-run demo does not require host Node dependencies.
|
|
115
|
+
|
|
116
|
+
## Config Missing
|
|
117
|
+
|
|
118
|
+
What happened:
|
|
119
|
+
|
|
120
|
+
```text
|
|
121
|
+
Runner config not found at synapsor.runner.json.
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Why it matters:
|
|
125
|
+
|
|
126
|
+
Own-database MCP setup needs a reviewed config before serving tools.
|
|
127
|
+
|
|
128
|
+
Fix:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
npx -y -p @synapsor/runner@alpha synapsor-runner init --from-env DATABASE_URL --mode review --wizard
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Or pass an example config:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
npx -y -p @synapsor/runner@alpha synapsor-runner tools preview --config ./examples/mcp-postgres-billing/synapsor.runner.json --store ./.synapsor/local.db
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## SQLite Store Missing
|
|
141
|
+
|
|
142
|
+
What happened:
|
|
143
|
+
|
|
144
|
+
```text
|
|
145
|
+
SQLite local store not found at ./.synapsor/local.db.
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Why it matters:
|
|
149
|
+
|
|
150
|
+
The local UI and replay read proposal/evidence state from the store.
|
|
151
|
+
|
|
152
|
+
Fix:
|
|
153
|
+
|
|
154
|
+
Run a demo or create a proposal first:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
./scripts/try-synapsor.sh
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
or:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
corepack pnpm demo:reference
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## DB URL Env Var Missing
|
|
167
|
+
|
|
168
|
+
What happened:
|
|
169
|
+
|
|
170
|
+
```text
|
|
171
|
+
SYNAPSOR_DATABASE_READ_URL is not set.
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Why it matters:
|
|
175
|
+
|
|
176
|
+
Configured capabilities need a read credential to inspect/propose against your
|
|
177
|
+
database.
|
|
178
|
+
|
|
179
|
+
Fix:
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
export SYNAPSOR_DATABASE_READ_URL="<read-only-url>"
|
|
183
|
+
npx -y -p @synapsor/runner@alpha synapsor-runner doctor --config synapsor.runner.json
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Read/Write Credential Split Failed
|
|
187
|
+
|
|
188
|
+
What happened:
|
|
189
|
+
|
|
190
|
+
```text
|
|
191
|
+
Read and write env vars resolve to the same credential.
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Why it matters:
|
|
195
|
+
|
|
196
|
+
Read/proposal authority and writeback authority must be separated.
|
|
197
|
+
|
|
198
|
+
Fix:
|
|
199
|
+
|
|
200
|
+
Use a read-only credential for MCP reads and a separate writer credential only
|
|
201
|
+
for trusted apply.
|
|
202
|
+
|
|
203
|
+
## MCP Client Config Contains A Secret
|
|
204
|
+
|
|
205
|
+
What happened:
|
|
206
|
+
|
|
207
|
+
Doctor reports a generated MCP client config appears to contain a database URL,
|
|
208
|
+
password, or token.
|
|
209
|
+
|
|
210
|
+
Why it matters:
|
|
211
|
+
|
|
212
|
+
MCP clients must receive only the local runner command and args.
|
|
213
|
+
|
|
214
|
+
Fix:
|
|
215
|
+
|
|
216
|
+
Regenerate the snippet:
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
npx -y -p @synapsor/runner@alpha synapsor-runner mcp config claude-desktop \
|
|
220
|
+
--absolute-paths \
|
|
221
|
+
--config ./synapsor.runner.json \
|
|
222
|
+
--store ./.synapsor/local.db
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Keep database URLs in environment variables, not client JSON.
|
|
226
|
+
|
|
227
|
+
## Demo Did Not Prove The Boundary
|
|
228
|
+
|
|
229
|
+
What happened:
|
|
230
|
+
|
|
231
|
+
```text
|
|
232
|
+
Demo did not prove the Synapsor boundary.
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Why it matters:
|
|
236
|
+
|
|
237
|
+
The first-run demo must prove semantic tools, proposal creation, source row
|
|
238
|
+
unchanged, approval outside MCP, guarded writeback/conflict, replay, and no
|
|
239
|
+
secret leakage.
|
|
240
|
+
|
|
241
|
+
Fix:
|
|
242
|
+
|
|
243
|
+
Inspect the printed log path, then reset:
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
./scripts/try-synapsor.sh --reset
|
|
247
|
+
./scripts/try-synapsor.sh
|
|
248
|
+
```
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# Writeback Executors
|
|
2
|
+
|
|
3
|
+
Synapsor Runner separates proposal authority from execution authority.
|
|
4
|
+
|
|
5
|
+
The model-facing MCP server can inspect and propose. It cannot approve or
|
|
6
|
+
commit. After a human or trusted process approves a proposal, the local runner
|
|
7
|
+
uses a configured writeback executor.
|
|
8
|
+
|
|
9
|
+
## `sql_update`
|
|
10
|
+
|
|
11
|
+
`sql_update` is the default executor.
|
|
12
|
+
|
|
13
|
+
It applies one guarded `UPDATE` through the database adapter:
|
|
14
|
+
|
|
15
|
+
- fixed schema/table from reviewed config;
|
|
16
|
+
- fixed primary key column;
|
|
17
|
+
- tenant guard;
|
|
18
|
+
- allowed-column validation;
|
|
19
|
+
- conflict/version guard;
|
|
20
|
+
- idempotency key;
|
|
21
|
+
- affected-row check;
|
|
22
|
+
- terminal receipt in replay.
|
|
23
|
+
|
|
24
|
+
Use this when the trusted runner is allowed to update the selected business row
|
|
25
|
+
directly.
|
|
26
|
+
|
|
27
|
+
The source config controls which writer env var is used:
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"sources": {
|
|
32
|
+
"local_postgres": {
|
|
33
|
+
"engine": "postgres",
|
|
34
|
+
"read_url_env": "SYNAPSOR_DATABASE_READ_URL",
|
|
35
|
+
"write_url_env": "SYNAPSOR_DATABASE_WRITE_URL"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
For `synapsor-runner apply --job ... --config ...`, set the env var named by
|
|
42
|
+
`write_url_env`. `SYNAPSOR_DATABASE_URL` is only a legacy fallback for direct
|
|
43
|
+
worker flows that do not pass a local config.
|
|
44
|
+
|
|
45
|
+
Direct SQL writeback also stores idempotency receipts in the source database.
|
|
46
|
+
By default it runs:
|
|
47
|
+
|
|
48
|
+
```sql
|
|
49
|
+
CREATE TABLE IF NOT EXISTS synapsor_writeback_receipts (...);
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
That means the writer needs permission to create and write that receipt table in
|
|
53
|
+
the target schema/database, or the table must be pre-created by an administrator
|
|
54
|
+
and granted to the writer. If you do not want Runner to create a table in the
|
|
55
|
+
application schema, create a dedicated schema/database for receipts where your
|
|
56
|
+
database policy allows it, or use `http_handler`/`command_handler` so your
|
|
57
|
+
application owns receipt storage and business writes.
|
|
58
|
+
|
|
59
|
+
Use the helper commands before enabling direct SQL writeback:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
npx -y -p @synapsor/runner@alpha synapsor-runner writeback doctor --config ./synapsor.runner.json
|
|
63
|
+
npx -y -p @synapsor/runner@alpha synapsor-runner writeback migration --engine postgres
|
|
64
|
+
npx -y -p @synapsor/runner@alpha synapsor-runner writeback grants --engine postgres --writer-role app_writer
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`writeback doctor --check-db` connects with the configured writer credential and
|
|
68
|
+
checks the receipt table path. That check can create the receipt table if the
|
|
69
|
+
writer has `CREATE`, so run it only against staging/disposable databases or
|
|
70
|
+
after reviewing the printed migration/grants.
|
|
71
|
+
|
|
72
|
+
## `http_handler`
|
|
73
|
+
|
|
74
|
+
Use `http_handler` when your application/API should own business execution.
|
|
75
|
+
|
|
76
|
+
The approved proposal becomes a structured HTTP request to an internal handler.
|
|
77
|
+
The handler URL and bearer token come from environment variables, not config
|
|
78
|
+
literal values.
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"executors": {
|
|
83
|
+
"billing_api": {
|
|
84
|
+
"type": "http_handler",
|
|
85
|
+
"url_env": "SYNAPSOR_BILLING_HANDLER_URL",
|
|
86
|
+
"method": "POST",
|
|
87
|
+
"auth": {
|
|
88
|
+
"type": "bearer_env",
|
|
89
|
+
"token_env": "SYNAPSOR_BILLING_HANDLER_TOKEN"
|
|
90
|
+
},
|
|
91
|
+
"timeout_ms": 5000
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
"capabilities": [
|
|
95
|
+
{
|
|
96
|
+
"name": "billing.propose_late_fee_waiver",
|
|
97
|
+
"kind": "proposal",
|
|
98
|
+
"executor": "billing_api"
|
|
99
|
+
}
|
|
100
|
+
]
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Run after approval:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
npx -y -p @synapsor/runner@alpha synapsor-runner apply \
|
|
108
|
+
--proposal wrp_123 \
|
|
109
|
+
--config ./synapsor.runner.json \
|
|
110
|
+
--store ./.synapsor/local.db
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The handler receives proposal fields, the exact patch, evidence metadata,
|
|
114
|
+
guards, and an idempotency key. It does not receive arbitrary model SQL or DB
|
|
115
|
+
credentials from Synapsor Runner.
|
|
116
|
+
|
|
117
|
+
Handler responses:
|
|
118
|
+
|
|
119
|
+
```json
|
|
120
|
+
{
|
|
121
|
+
"status": "applied",
|
|
122
|
+
"rows_affected": 1,
|
|
123
|
+
"previous_version": "2026-06-20T14:31:08Z",
|
|
124
|
+
"new_version": "2026-06-20T14:34:19Z",
|
|
125
|
+
"source_database_mutated": true
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Allowed terminal statuses:
|
|
130
|
+
|
|
131
|
+
- `applied`
|
|
132
|
+
- `already_applied`
|
|
133
|
+
- `conflict`
|
|
134
|
+
- `failed`
|
|
135
|
+
|
|
136
|
+
Non-2xx responses and timeouts become failed execution receipts. The terminal
|
|
137
|
+
receipt is stored in replay.
|
|
138
|
+
|
|
139
|
+
Use your application/API for business logic. Use Synapsor Runner for proposal,
|
|
140
|
+
approval, evidence, policy boundary, and replay.
|
|
141
|
+
|
|
142
|
+
This is the recommended path for writes that are richer than the current
|
|
143
|
+
`sql_update` scope, such as:
|
|
144
|
+
|
|
145
|
+
- creating a refund review;
|
|
146
|
+
- inserting an account credit row;
|
|
147
|
+
- opening a support ticket;
|
|
148
|
+
- updating multiple related rows in one app transaction.
|
|
149
|
+
|
|
150
|
+
Starter templates are in:
|
|
151
|
+
|
|
152
|
+
```text
|
|
153
|
+
examples/app-owned-writeback/
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Concrete business-action examples are in:
|
|
157
|
+
|
|
158
|
+
```text
|
|
159
|
+
examples/app-owned-writeback/business-actions.md
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Or generate one into your app:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
npx -y -p @synapsor/runner@alpha synapsor-runner handler template node-fastify \
|
|
166
|
+
--output ./synapsor-writeback-handler.mjs
|
|
167
|
+
|
|
168
|
+
npx -y -p @synapsor/runner@alpha synapsor-runner handler template python-fastapi \
|
|
169
|
+
--output ./synapsor_writeback_handler.py
|
|
170
|
+
|
|
171
|
+
npx -y -p @synapsor/runner@alpha synapsor-runner handler template command \
|
|
172
|
+
--output ./synapsor-command-handler.mjs
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## `command_handler`
|
|
176
|
+
|
|
177
|
+
`command_handler` is a local integration path for scripts:
|
|
178
|
+
|
|
179
|
+
```json
|
|
180
|
+
{
|
|
181
|
+
"executors": {
|
|
182
|
+
"local_billing_script": {
|
|
183
|
+
"type": "command_handler",
|
|
184
|
+
"command_env": "SYNAPSOR_BILLING_HANDLER_COMMAND",
|
|
185
|
+
"timeout_ms": 5000
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
The command receives the same structured JSON request on stdin and should print
|
|
192
|
+
a JSON receipt body on stdout.
|
|
193
|
+
|
|
194
|
+
Use `examples/app-owned-writeback/command-handler.mjs` as a starting point when
|
|
195
|
+
your safest apply path is an app script or job runner.
|
|
196
|
+
|
|
197
|
+
## Safety Boundary
|
|
198
|
+
|
|
199
|
+
Executor secrets are never exposed over MCP. The model never receives:
|
|
200
|
+
|
|
201
|
+
- approval tools;
|
|
202
|
+
- commit tools;
|
|
203
|
+
- write credentials;
|
|
204
|
+
- handler bearer tokens;
|
|
205
|
+
- arbitrary SQL authority;
|
|
206
|
+
- tenant/principal authority.
|
|
207
|
+
|
|
208
|
+
`MCP tool call = request/proposal authority. Trusted runner = execution
|
|
209
|
+
authority.`
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# App-Owned Writeback Templates
|
|
2
|
+
|
|
3
|
+
Use app-owned writeback when an approved Synapsor proposal should be executed
|
|
4
|
+
by your application service instead of Runner writing SQL directly.
|
|
5
|
+
|
|
6
|
+
This is the right path for rich business actions:
|
|
7
|
+
|
|
8
|
+
- create a refund review;
|
|
9
|
+
- insert an account credit row;
|
|
10
|
+
- open a support ticket;
|
|
11
|
+
- update multiple related rows through your app service.
|
|
12
|
+
|
|
13
|
+
The model-facing MCP tool still only creates a proposal. Approval happens
|
|
14
|
+
outside MCP. After approval, `synapsor-runner apply` sends a structured request
|
|
15
|
+
to your handler, and the handler returns an execution receipt for replay.
|
|
16
|
+
|
|
17
|
+
## Config Snippet
|
|
18
|
+
|
|
19
|
+
Add an executor and point one proposal capability at it:
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"executors": {
|
|
24
|
+
"app_writeback_api": {
|
|
25
|
+
"type": "http_handler",
|
|
26
|
+
"url_env": "SYNAPSOR_APP_WRITEBACK_URL",
|
|
27
|
+
"method": "POST",
|
|
28
|
+
"auth": {
|
|
29
|
+
"type": "bearer_env",
|
|
30
|
+
"token_env": "SYNAPSOR_APP_WRITEBACK_TOKEN"
|
|
31
|
+
},
|
|
32
|
+
"timeout_ms": 5000
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"capabilities": [
|
|
36
|
+
{
|
|
37
|
+
"name": "refunds.propose_refund_review",
|
|
38
|
+
"kind": "proposal",
|
|
39
|
+
"executor": "app_writeback_api"
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Run after approval:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
export SYNAPSOR_APP_WRITEBACK_URL="http://127.0.0.1:8787/synapsor/writeback"
|
|
49
|
+
export SYNAPSOR_APP_WRITEBACK_TOKEN="dev-handler-token"
|
|
50
|
+
|
|
51
|
+
synapsor-runner apply \
|
|
52
|
+
--proposal wrp_... \
|
|
53
|
+
--config ./synapsor.runner.json \
|
|
54
|
+
--store ./.synapsor/local.db
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Handler Request Shape
|
|
58
|
+
|
|
59
|
+
Your handler receives JSON like:
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"schema_version": "synapsor.handler-writeback.v1",
|
|
64
|
+
"writeback_job_id": "hwb_wrp_...",
|
|
65
|
+
"proposal_id": "wrp_...",
|
|
66
|
+
"idempotency_key": "wrp_...",
|
|
67
|
+
"change_set": {
|
|
68
|
+
"action": "refunds.propose_refund_review",
|
|
69
|
+
"scope": {
|
|
70
|
+
"tenant_id": "acme",
|
|
71
|
+
"object_id": "INV-3001"
|
|
72
|
+
},
|
|
73
|
+
"before": {},
|
|
74
|
+
"patch": {},
|
|
75
|
+
"after": {},
|
|
76
|
+
"guards": {},
|
|
77
|
+
"evidence": {
|
|
78
|
+
"bundle_id": "ev_..."
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
"executor": "app_writeback_api",
|
|
82
|
+
"dry_run": false
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Do not trust request fields blindly. Your service should re-check tenant,
|
|
87
|
+
principal, authorization, idempotency, row versions, and business rules before
|
|
88
|
+
mutating state.
|
|
89
|
+
|
|
90
|
+
## Handler Response Shape
|
|
91
|
+
|
|
92
|
+
Return one terminal receipt:
|
|
93
|
+
|
|
94
|
+
```json
|
|
95
|
+
{
|
|
96
|
+
"status": "applied",
|
|
97
|
+
"rows_affected": 1,
|
|
98
|
+
"previous_version": "2026-06-20T14:31:08Z",
|
|
99
|
+
"new_version": "2026-06-20T14:34:19Z",
|
|
100
|
+
"source_database_mutated": true
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Allowed statuses:
|
|
105
|
+
|
|
106
|
+
- `applied`
|
|
107
|
+
- `already_applied`
|
|
108
|
+
- `conflict`
|
|
109
|
+
- `failed`
|
|
110
|
+
|
|
111
|
+
## Templates
|
|
112
|
+
|
|
113
|
+
- `node-fastify-handler.mjs`: HTTP handler template for a Node/Fastify service.
|
|
114
|
+
- `python-fastapi-handler.py`: HTTP handler template for a Python/FastAPI service.
|
|
115
|
+
- `command-handler.mjs`: local command handler template for scripts.
|
|
116
|
+
- `business-actions.md`: concrete examples for refund reviews, account
|
|
117
|
+
credits, support tickets, and multi-row app transactions.
|
|
118
|
+
|
|
119
|
+
Each template returns safe demo receipts and marks where your application
|
|
120
|
+
should run its own transaction.
|