@synapsor/runner 0.1.0-alpha.15 → 0.1.0-alpha.17

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/docs/README.md CHANGED
@@ -20,7 +20,7 @@ detail.
20
20
  capabilities, model-facing descriptions, result envelopes, trusted context,
21
21
  and writeback guards. JSON Schema:
22
22
  `../schemas/synapsor.runner.schema.json`.
23
- - [Result Envelope v2](result-envelope-v2.md): the opt-in
23
+ - [Result Envelope v2](result-envelope-v2.md): the generated-config default
24
24
  `ok`/`summary`/`data`/`proposal`/`error` response shape for MCP tools.
25
25
  - [Handler Helper](handler-helper.md): TypeScript helper for safe app-owned
26
26
  rich-write handlers.
@@ -60,8 +60,8 @@ detail.
60
60
  for approved proposals.
61
61
  - [App-Owned Executors](app-owned-executors.md): short entry point for rich
62
62
  business transactions handled by your app.
63
- - `synapsor-runner events tail`: local lifecycle events such as
64
- `proposal_created`, `proposal_approved`, `writeback_applied`, and
63
+ - `synapsor-runner events tail` and `events webhook`: local lifecycle events
64
+ such as `proposal_created`, `proposal_approved`, `writeback_applied`, and
65
65
  `writeback_conflict`.
66
66
 
67
67
  Useful examples:
@@ -17,6 +17,13 @@ The model-facing MCP tool only creates a proposal. Approval happens outside MCP.
17
17
  After approval, Runner calls your `http_handler` or `command_handler`, records
18
18
  the receipt, and includes the result in replay.
19
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
+
20
27
  A handler is your application endpoint or script. It is not a second Synapsor
21
28
  package that users need to install. Install `@synapsor/runner`, then generate
22
29
  or copy a handler template only when your approved write needs app-owned
@@ -13,6 +13,12 @@ Current alpha scope:
13
13
  business transactions;
14
14
  - stdio MCP, Streamable HTTP MCP, and a small JSON-RPC bridge.
15
15
 
16
+ Stable `0.1.x` compatibility, once `0.1.0` is promoted to `latest`, covers the
17
+ documented `synapsor-runner` binary, config schema version `1`, result envelope
18
+ v2 with v1 opt-out, stdio/Streamable HTTP MCP surfaces, documented MCP client
19
+ snippets, proposal/evidence/replay inspection commands, direct SQL writeback,
20
+ and app-owned executor contracts.
21
+
16
22
  Out of scope:
17
23
 
18
24
  - raw `execute_sql`;
@@ -179,7 +179,8 @@ The wizard:
179
179
  mode, semantic capability names, and proposal patch mappings;
180
180
  - asks review-mode users to choose direct guarded SQL writeback, an app-owned
181
181
  HTTP handler, or an app-owned command handler;
182
- - previews the MCP tools and what is not exposed;
182
+ - previews the MCP tools and what is not exposed, then lets you revise visible
183
+ fields or capability names before writing files;
183
184
  - attempts a first smoke call when you supplied a real object id and the
184
185
  required trusted env vars are present;
185
186
  - writes the generated config, `.env.example`, and MCP client snippets only
@@ -208,12 +209,17 @@ npx -y -p @synapsor/runner@alpha synapsor-runner init \
208
209
  --mode review \
209
210
  --visible-columns id,tenant_id,late_fee_cents,waiver_reason,updated_at \
210
211
  --allowed-columns late_fee_cents,waiver_reason \
211
- --patch-fixed late_fee_cents=0 \
212
- --patch-from-arg waiver_reason=reason \
213
- --numeric-bound late_fee_cents=0:5500 \
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 \
214
216
  --write-url-env SYNAPSOR_DATABASE_WRITE_URL
215
217
  ```
216
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
+
217
223
  For app-owned writeback, replace the direct writer env with a handler executor:
218
224
 
219
225
  ```bash
@@ -225,13 +231,19 @@ npx -y -p @synapsor/runner@alpha synapsor-runner init \
225
231
  --namespace billing \
226
232
  --object-name invoice \
227
233
  --mode review \
228
- --patch-fixed late_fee_cents=0 \
229
- --patch-from-arg waiver_reason=reason \
234
+ --tenant-column tenant_id \
235
+ --id-arg invoice_id \
236
+ --patch late_fee_cents=fixed:0,waiver_reason=arg:reason \
230
237
  --writeback http_handler \
231
238
  --handler-url-env APP_WRITEBACK_URL \
232
- --handler-token-env APP_WRITEBACK_TOKEN
239
+ --handler-token-env APP_WRITEBACK_TOKEN \
240
+ --emit-handler
233
241
  ```
234
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
+
235
247
  Use `--writeback command_handler --handler-command-env APP_WRITEBACK_COMMAND`
236
248
  when your app-owned writer is a local command/script instead of HTTP.
237
249
 
@@ -244,8 +256,7 @@ npx -y -p @synapsor/runner@alpha synapsor-runner init \
244
256
  --namespace billing \
245
257
  --object-name invoice \
246
258
  --mode review \
247
- --patch-fixed late_fee_cents=0 \
248
- --patch-from-arg waiver_reason=reason
259
+ --patch late_fee_cents=fixed:0,waiver_reason=arg:reason
249
260
  ```
250
261
 
251
262
  The command uses inspected metadata for primary-key, tenant-key, conflict-column,
@@ -253,15 +264,15 @@ and default-visible-column suggestions. If a suggestion is ambiguous or missing,
253
264
  pass explicit flags such as `--primary-key`, `--tenant-key`, and
254
265
  `--conflict-column`.
255
266
 
256
- Review mode requires at least one explicit `--patch-fixed` or
257
- `--patch-from-arg` mapping. Use `--mode read_only` if you only want an inspect
258
- 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.
259
270
 
260
271
  For bounded business actions, add reviewed value guards:
261
272
 
262
273
  ```bash
263
- --numeric-bound credit_cents=0:10000
264
- --transition-guard status=open:pending_review
274
+ --patch-bounds credit_cents=0:10000
275
+ --status-guards status=open:pending_review
265
276
  ```
266
277
 
267
278
  `--numeric-bound` keeps a proposed numeric column inside a fixed range before a
@@ -279,6 +290,7 @@ Create `onboarding-selection.json` from one table and one safe business action.
279
290
  "version": 1,
280
291
  "engine": "postgres",
281
292
  "mode": "review",
293
+ "result_format": 2,
282
294
  "read_url_env": "SYNAPSOR_DATABASE_READ_URL",
283
295
  "write_url_env": "SYNAPSOR_DATABASE_WRITE_URL",
284
296
  "schema": "public",
@@ -288,6 +300,8 @@ Create `onboarding-selection.json` from one table and one safe business action.
288
300
  "conflict_column": "updated_at",
289
301
  "namespace": "billing",
290
302
  "object_name": "invoice",
303
+ "read_tool": "billing.inspect_invoice",
304
+ "proposal_tool": "billing.propose_late_fee_waiver",
291
305
  "visible_columns": ["id", "tenant_id", "late_fee_cents", "waiver_reason", "updated_at"],
292
306
  "allowed_columns": ["late_fee_cents", "waiver_reason"],
293
307
  "patch": {
@@ -386,6 +400,20 @@ export SYNAPSOR_TENANT_ID="acme"
386
400
  export SYNAPSOR_PRINCIPAL="local_operator"
387
401
  export SYNAPSOR_RUNNER_HTTP_TOKEN="dev-local-token"
388
402
 
403
+ npx -y -p @synapsor/runner@alpha 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
389
417
  npx -y -p @synapsor/runner@alpha synapsor-runner mcp serve-streamable-http \
390
418
  --config ./synapsor.runner.json \
391
419
  --store ./.synapsor/local.db \
@@ -17,6 +17,13 @@ The model-facing MCP tool still creates a proposal only. A human/operator
17
17
  approves outside MCP. After approval, Runner sends the structured writeback
18
18
  request to your handler.
19
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
+
20
27
  ## Scope
21
28
 
22
29
  Current alpha scope:
@@ -41,7 +48,7 @@ request/receipt schema and the FastAPI template in `examples/app-owned-writeback
41
48
 
42
49
  The helper implementation exists in this source repo under `packages/handler`
43
50
  and is used by the app-owned executor example and tests. It is not published as
44
- a standalone `@synapsor/handler` npm package yet.
51
+ a standalone npm package yet.
45
52
 
46
53
  If you installed `@synapsor/runner` from npm, use one of these alpha paths:
47
54
 
@@ -50,10 +57,6 @@ If you installed `@synapsor/runner` from npm, use one of these alpha paths:
50
57
  - run `examples/mcp-postgres-billing-app-handler/`, which includes a bundled
51
58
  `synapsor-handler.mjs` shim inside the runner package.
52
59
 
53
- The `@synapsor/handler` import below is the source-checkout API and the planned
54
- standalone package API. Do not `npm install @synapsor/handler` until that
55
- package is published.
56
-
57
60
  ## Schemas
58
61
 
59
62
  Published schemas:
@@ -67,8 +70,12 @@ the alpha migration.
67
70
 
68
71
  ## TypeScript Usage From A Source Checkout
69
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
+
70
77
  ```ts
71
- import { createWritebackHandler } from "@synapsor/handler";
78
+ import { createWritebackHandler } from "../packages/handler/src/index.js";
72
79
 
73
80
  export const handler = createWritebackHandler({
74
81
  tokenEnv: "SYNAPSOR_APP_HANDLER_TOKEN",
@@ -274,6 +274,7 @@ or prune it without touching your source Postgres/MySQL database:
274
274
  ```bash
275
275
  synapsor-runner store stats --store ./.synapsor/local.db
276
276
  synapsor-runner events tail --store ./.synapsor/local.db
277
+ synapsor-runner events webhook --url http://127.0.0.1:8788/synapsor/events --kind proposal_created --store ./.synapsor/local.db
277
278
  synapsor-runner store vacuum --store ./.synapsor/local.db
278
279
  synapsor-runner store prune --store ./.synapsor/local.db --older-than 30d --dry-run
279
280
  synapsor-runner store prune --store ./.synapsor/local.db --older-than 30d --yes
@@ -286,6 +287,11 @@ ledger, including proposal creation, approval/rejection, writeback jobs, and
286
287
  writeback applied/conflict/failed receipts. Add `--follow` to keep polling a
287
288
  running local store.
288
289
 
290
+ `events webhook` pushes the same local lifecycle events to a local/dev/staging
291
+ HTTP endpoint, one event envelope per POST. Use it for a review UI, Slack bridge,
292
+ or app-local notification path when polling is awkward. It is not a hosted
293
+ central ledger and does not expose database credentials.
294
+
289
295
  `store prune` defaults to dry-run. `store reset` requires `--yes` and removes
290
296
  only the local SQLite ledger files. MCP server modes write a small active-store
291
297
  lease next to the SQLite file; destructive store operations refuse while that
@@ -11,6 +11,81 @@ npx -y -p @synapsor/runner@alpha synapsor-runner demo --quick
11
11
  The OSS runner command is `synapsor-runner`. The `synapsor` command is reserved
12
12
  for the Synapsor Cloud CLI.
13
13
 
14
+ ## 0.1.0-alpha.17
15
+
16
+ ### Scripted Onboarding
17
+
18
+ - `onboard db` and `init` now have a prompt-free path for scripts, CI, and LLM
19
+ agents. Use `--yes`, `--non-interactive`, or `--answers <file.json>`.
20
+ - Added friendly flags that match the first-run mental model:
21
+ `--tenant-column`, `--id-arg`, `--patch column=fixed:value|arg:name`,
22
+ `--patch-bounds`, `--status-guards`, `--read-description`,
23
+ `--read-returns-hint`, `--read-tool`, `--proposal-tool`,
24
+ `--handler-output`, and `--emit-handler`.
25
+ - Answers-file onboarding writes the reviewed config, `.env.example`, MCP
26
+ snippets, and optional handler template without opening a TTY prompt.
27
+ - When `--namespace` is omitted, generated capability names now derive a
28
+ namespace from the selected table instead of falling back to `source.*`.
29
+ - The guided wizard now has a final "what I am about to write" preview where
30
+ users can revise visible fields or capability names before files are written.
31
+ - README Start Here now tells users to run `tools preview` and `smoke call`
32
+ before wiring an MCP client.
33
+ - `events webhook` / `events push` can POST local proposal/writeback lifecycle
34
+ events to a local/dev/staging HTTP endpoint for review UIs or notifications.
35
+
36
+ ### Writeback Readiness
37
+
38
+ - App-owned executor configs generated by Runner now mark the Runner source as
39
+ `read_only: true` when no writer env is supplied. `config validate` no longer
40
+ reports `WRITEBACK_DISABLED` for handler-owned writeback paths.
41
+ - Direct SQL review-mode proposals still surface `WRITEBACK_DISABLED` if the
42
+ source has no `write_url_env`, because Runner cannot apply those proposals
43
+ without a trusted writer connection.
44
+
45
+ ### Handler Docs
46
+
47
+ - README and runner README now include a short "How An External Handler Works"
48
+ section: agent proposes, human approves outside MCP, Runner POSTs to your
49
+ endpoint, and your code writes in its own transaction.
50
+ - Published docs/examples no longer include install-looking imports for a
51
+ separate handler package. Use `synapsor-runner handler template ...` or the
52
+ bundled `synapsor-handler.mjs` shim in the app-owned example.
53
+
54
+ ## 0.1.0-alpha.16
55
+
56
+ ### Review-Mode Bring-Up
57
+
58
+ - Added `synapsor-runner up` as the local review-mode orientation command. It
59
+ validates the config/store, checks active store leases, summarizes
60
+ model-facing tools, identifies direct SQL versus app-owned executor writeback
61
+ paths, and prints the next smoke, approval, apply, replay, UI, and doctor
62
+ commands.
63
+ - `up` is guidance-only by default. `up --serve` starts the standard MCP
64
+ Streamable HTTP server after the same validation and guidance.
65
+ - `up --dry-run` gives the full checklist without starting a server.
66
+ - `up --handler-check` or `up --with-handler` runs the redacted handler
67
+ env/reachability doctor path before serving.
68
+ - The guided wizard now writes model-facing capability descriptions,
69
+ per-argument descriptions, returns hints, and defaults generated configs to
70
+ `result_format: 2`.
71
+ - `result_format: 2` gives MCP clients a stable envelope with `ok`, `summary`,
72
+ `data`, `proposal`, `error`, `evidence`, `source_database_changed`, and
73
+ `_meta.canonical_capability`. Pass `--result-format v1` or
74
+ `"result_format": 1` only when an older client needs the legacy shape.
75
+ - `tools list`, `tools list --aliases`, and
76
+ `mcp client-config --include-instructions` help users inspect exposed tools
77
+ and generate client snippets without source reading.
78
+
79
+ ### Handler Security
80
+
81
+ - Generated handler templates, template-list output, app-owned writeback docs,
82
+ and examples now explicitly warn that the app handler owns the final business
83
+ write. Handlers must re-check tenant/scope, expected-version or conflict
84
+ guard, idempotency, allowed business action, transaction/rollback, and safe
85
+ error receipts before mutating application state.
86
+ - The guided review-mode wizard can now write a starter handler template when
87
+ the app-owned HTTP or command handler path is selected.
88
+
14
89
  ## 0.1.0-alpha.15
15
90
 
16
91
  ### Handler Wording Clarification
@@ -23,10 +98,10 @@ for the Synapsor Cloud CLI.
23
98
 
24
99
  ### Handler Helper And Changelog Clarity
25
100
 
26
- - Public docs now state that `@synapsor/handler` is not published as a
27
- standalone npm package yet. The helper currently ships as source under
28
- `packages/handler` and as the bundled `synapsor-handler.mjs` shim in the
29
- app-owned executor example included with `@synapsor/runner`.
101
+ - Public docs now state that the handler helper is not a standalone npm package
102
+ yet. The helper currently ships as source under `packages/handler` and as the
103
+ bundled `synapsor-handler.mjs` shim in the app-owned executor example
104
+ included with `@synapsor/runner`.
30
105
  - `CHANGELOG.md` is included in the `@synapsor/runner` npm tarball.
31
106
 
32
107
  ## 0.1.0-alpha.13
@@ -160,6 +235,9 @@ for the Synapsor Cloud CLI.
160
235
  writeback jobs, execution receipts, and events.
161
236
  - `synapsor-runner events tail` prints local lifecycle events from the SQLite
162
237
  ledger and can follow new proposal/writeback events while a local flow runs.
238
+ - `synapsor-runner events webhook` pushes those local lifecycle events to a
239
+ local/dev/staging HTTP endpoint for review UIs or notifications without
240
+ polling. It is not a hosted central ledger.
163
241
  - MCP server modes write an active-store lease next to the local SQLite file.
164
242
  Destructive `store prune --yes` refuses while that lease points at a live
165
243
  process unless `--force` is provided.
@@ -209,13 +287,18 @@ changing. A stable `0.1.0` release should only be tagged after:
209
287
  For the local tarball before publish, run:
210
288
 
211
289
  ```bash
212
- ./scripts/verify-packed-runner.sh
213
- ./scripts/verify-packed-own-db.sh
290
+ ./scripts/verify-release-gate.sh
214
291
  ```
215
292
 
216
293
  After publishing an alpha, verify the public package from a clean temporary
217
294
  directory:
218
295
 
219
296
  ```bash
220
- ./scripts/verify-published-alpha.sh 0.1.0-alpha.15
297
+ VERIFY_PUBLISHED_ALPHA=1 ./scripts/verify-release-gate.sh 0.1.0-alpha.17
298
+ ```
299
+
300
+ After publishing/promoting stable `latest`, verify the stable channel:
301
+
302
+ ```bash
303
+ ./scripts/verify-published-stable.sh 0.1.0
221
304
  ```
@@ -5,7 +5,7 @@ or an exact version:
5
5
 
6
6
  ```bash
7
7
  npx -y -p @synapsor/runner@alpha synapsor-runner demo --quick
8
- npm install -g @synapsor/runner@0.1.0-alpha.15
8
+ npm install -g @synapsor/runner@0.1.0-alpha.17
9
9
  ```
10
10
 
11
11
  Do not rely on the untagged `latest` dist-tag until a stable release is
@@ -38,16 +38,52 @@ A stable `0.1.0` release should only be tagged after:
38
38
  - npm README commands match the published package;
39
39
  - `synapsor-runner demo --quick` works from a clean directory;
40
40
  - own-database onboarding works from a clean directory;
41
+ - one-command review-mode `synapsor-runner up` is verified from a clean
42
+ directory and clearly prints model-facing tools, writeback path, handler
43
+ requirements, and next commands;
44
+ - review-mode wizard output is verified for one read capability plus one
45
+ proposal capability;
46
+ - handler template security warnings are verified in docs, CLI output, and
47
+ generated templates;
41
48
  - stdio MCP and Streamable HTTP MCP are both verified;
42
49
  - OpenAI alias mode is verified;
43
50
  - direct SQL writeback requirements are documented and tested;
44
51
  - app-owned executor requirements are documented and tested;
45
52
  - local evidence/proposal/receipt/replay inspection works;
46
53
  - current limitations are accurate.
54
+ - at least one external developer can follow the README without reading source;
55
+ - there are no known docs/code mismatches around transport, credentials,
56
+ receipt tables, or handler expectations.
57
+
58
+ ## Stable Compatibility Promise
59
+
60
+ After `0.1.0` is promoted to the untagged `latest` dist-tag, Synapsor Runner
61
+ keeps these public surfaces compatible through the `0.1.x` line unless a
62
+ release note marks a deprecation first:
63
+
64
+ - the `synapsor-runner` binary name and README quickstart commands;
65
+ - `synapsor.runner.json` schema version `1` for documented fields;
66
+ - result envelope v2 for new configs, with the documented v1 opt-out;
67
+ - stdio MCP and Streamable HTTP MCP command surfaces;
68
+ - generated MCP client snippets for documented clients;
69
+ - proposal, approval, guarded writeback, receipt, evidence, query-audit, and
70
+ replay inspection commands;
71
+ - direct SQL writeback and app-owned executor contracts documented in README
72
+ and `docs/writeback-executors.md`.
73
+
74
+ Stable does not promise production SLA, hosted Cloud features, compliance
75
+ certification, physical Postgres/MySQL branching, generic SQL writeback,
76
+ generic multi-row writes, or compatibility for undocumented local SQLite
77
+ internals. Local store migrations may happen inside `0.1.x`, but documented CLI
78
+ inspection commands should remain the supported way to read the store.
79
+
80
+ Serious alpha users should pin an exact alpha version in package.json, CI, and
81
+ MCP client snippets. Use `@alpha` only when intentionally testing the moving
82
+ preview channel.
47
83
 
48
84
  ## Result Envelope Migration
49
85
 
50
- `result_format: 2` is opt-in during alpha migration:
86
+ New `init` and `onboard db` configs default to:
51
87
 
52
88
  ```json
53
89
  {
@@ -55,32 +91,36 @@ A stable `0.1.0` release should only be tagged after:
55
91
  }
56
92
  ```
57
93
 
58
- or:
94
+ Existing hand-written configs without `result_format` keep the legacy runtime
95
+ default for compatibility. To force v2 or opt an older client back to v1, use:
59
96
 
60
97
  ```bash
61
98
  synapsor-runner mcp serve --result-format v2
62
99
  synapsor-runner mcp serve-streamable-http --result-format v2
100
+ synapsor-runner mcp serve --result-format v1
63
101
  ```
64
102
 
65
- v1 remains the default until the migration is explicitly called out in a future
66
- release note. v2 makes `ok` the only required branch point for MCP client code.
103
+ v2 makes `ok` the only required branch point for MCP client code. Do not remove
104
+ the v1 escape hatch until the stable compatibility policy says legacy result
105
+ shapes are no longer supported.
67
106
 
68
107
  ## Publish Checklist
69
108
 
70
109
  Before publishing a new alpha:
71
110
 
72
111
  ```bash
73
- corepack pnpm typecheck
74
- corepack pnpm exec vitest run packages/mcp-server/src/index.test.ts apps/runner/src/cli.test.ts
75
- ./scripts/verify-packed-runner.sh
76
- npm pack --dry-run
77
- git diff --check
112
+ ./scripts/verify-release-gate.sh
78
113
  ```
79
114
 
80
115
  After publishing:
81
116
 
82
117
  ```bash
83
- npm view @synapsor/runner@alpha version bin license
84
- npx -y -p @synapsor/runner@alpha synapsor-runner demo --quick --no-interactive
85
- npx -y -p @synapsor/runner@alpha synapsor-runner audit --example dangerous-db-mcp --format markdown
118
+ VERIFY_PUBLISHED_ALPHA=1 ./scripts/verify-release-gate.sh 0.1.0-alpha.17
119
+ ```
120
+
121
+ Before promoting `latest` to stable, bump and publish a non-prerelease version,
122
+ then verify the stable channel from a clean temporary directory:
123
+
124
+ ```bash
125
+ ./scripts/verify-published-stable.sh 0.1.0
86
126
  ```
@@ -144,5 +144,8 @@ INTERNAL
144
144
  ```
145
145
 
146
146
  Current alpha implementation redacts raw connection and driver messages from v2
147
- MCP results. Legacy result format v1 remains the default for compatibility in
148
- this alpha.
147
+ MCP results. New `init` and `onboard db` configs write `result_format: 2` by
148
+ default. Existing hand-written configs without `result_format` keep the legacy
149
+ runtime default for compatibility; pass `--result-format v2` when serving an
150
+ older config to force the v2 envelope, or `--result-format v1` for an older
151
+ client that still depends on the legacy shape.
@@ -56,7 +56,7 @@ Receipt the handler must return (today's status vocabulary, kept):
56
56
  ## Helper API (TypeScript — first-party, since the runner is TS/Node)
57
57
 
58
58
  ```ts
59
- import { createWritebackHandler } from "@synapsor/handler";
59
+ import { createWritebackHandler } from "../packages/handler/src/index.js";
60
60
 
61
61
  export const handler = createWritebackHandler({
62
62
  // 1. Authenticity: helper verifies bearer AND the HMAC signature for you.
@@ -15,7 +15,7 @@ The SQLite store is shared state, and the running server holds it open. Deleting
15
15
  Direct writeback does CREATE TABLE IF NOT EXISTS synapsor_writeback_receipts, which a least-privilege writer can't do (PG15+ no CREATE on public). Now documented, but doctor could detect it and print the exact GRANT/DDL (or a flag to pre-create). I had to invent the dedicated-schema trick myself.
16
16
 
17
17
  5. App-owned handlers re-implement security by hand.
18
- The executor contract is "match the example": you must re-check tenant, parse change_set, enforce the expected_version stale-row guard, and do idempotency yourself. It's easy to write an insecure handler (skip the version check, forget tenant scope). There's no handler SDK/helper and no request signing — the only auth is a bearer token, so the handler trusts the POST body's tenant/version. A @synapsor/handler helper (verify + parse + enforce guards, optional HMAC signature) would remove a whole class of mistakes.
18
+ The executor contract is "match the example": you must re-check tenant, parse change_set, enforce the expected_version stale-row guard, and do idempotency yourself. It's easy to write an insecure handler (skip the version check, forget tenant scope). There's no published handler SDK/helper and no request signing — the only auth is a bearer token, so the handler trusts the POST body's tenant/version. A first-party handler helper (verify + parse + enforce guards, optional HMAC signature) would remove a whole class of mistakes.
19
19
 
20
20
  6. Versioning discipline.
21
21
  @alpha is a moving tag and behavior changed meaningfully across alpha.6→11 (transport, arg types string→number, credential resolution). I had to pin and bump six times. A stable channel + changelog + semver promise is table stakes before serious devs build on it.
@@ -115,6 +115,13 @@ The handler receives proposal fields, the exact patch, evidence metadata,
115
115
  guards, and an idempotency key. It does not receive arbitrary model SQL or DB
116
116
  credentials from Synapsor Runner.
117
117
 
118
+ > **Important:** your app handler owns the final business write. Runner creates
119
+ > the proposal and calls your handler only after approval, but your handler must
120
+ > still enforce tenant/scope checks, expected-version or conflict guards,
121
+ > idempotency keys, allowed business actions, transaction/rollback, and safe
122
+ > error receipts. If you skip those checks, you can reintroduce cross-tenant
123
+ > writes, lost updates, or duplicate writes. Keep handler credentials out of MCP.
124
+
118
125
  When `signing_secret_env` is set, Runner signs the exact JSON body with HMAC
119
126
  SHA-256 and sends:
120
127
 
@@ -220,6 +227,12 @@ npx -y -p @synapsor/runner@alpha synapsor-runner handler template command \
220
227
  The command receives the same structured JSON request on stdin and should print
221
228
  a JSON receipt body on stdout.
222
229
 
230
+ > **Important:** command handlers have the same responsibility as HTTP
231
+ > handlers. Re-check tenant/scope, expected-version or conflict guard,
232
+ > idempotency, allowed business action, transaction/rollback, and safe error
233
+ > receipt before mutating state. Otherwise the script can reintroduce
234
+ > cross-tenant writes, lost updates, or duplicate writes.
235
+
223
236
  Use `examples/app-owned-writeback/command-handler.mjs` as a starting point when
224
237
  your safest apply path is an app script or job runner.
225
238
 
@@ -14,6 +14,13 @@ The model-facing MCP tool still only creates a proposal. Approval happens
14
14
  outside MCP. After approval, `synapsor-runner apply` sends a structured request
15
15
  to your handler, and the handler returns an execution receipt for replay.
16
16
 
17
+ > **Important:** your app handler owns the final business write. Runner creates
18
+ > the proposal and calls your handler only after approval, but your handler must
19
+ > still enforce tenant/scope checks, expected-version or conflict guards,
20
+ > idempotency keys, allowed business actions, transaction/rollback, and safe
21
+ > error receipts. If you skip those checks, you can reintroduce cross-tenant
22
+ > writes, lost updates, or duplicate writes. Keep handler credentials out of MCP.
23
+
17
24
  ## Config Snippet
18
25
 
19
26
  Add an executor and point one proposal capability at it:
@@ -26,6 +26,15 @@ if (request.dry_run) {
26
26
  }
27
27
 
28
28
  /*
29
+ * IMPORTANT: your app handler owns the final business write.
30
+ * Runner creates the proposal and calls your handler only after approval,
31
+ * but your handler must still enforce tenant/scope, expected-version or
32
+ * conflict guard, idempotency key, allowed business action,
33
+ * transaction/rollback, and safe error receipt.
34
+ *
35
+ * If you skip those checks, you can reintroduce cross-tenant writes,
36
+ * lost updates, or duplicate writes. Keep handler credentials out of MCP.
37
+ *
29
38
  * Put your app-owned command transaction here.
30
39
  *
31
40
  * Examples:
@@ -28,6 +28,15 @@ app.post("/synapsor/writeback", async (request, reply) => {
28
28
  }
29
29
 
30
30
  /*
31
+ * IMPORTANT: your app handler owns the final business write.
32
+ * Runner creates the proposal and calls your handler only after approval,
33
+ * but your handler must still enforce tenant/scope, expected-version or
34
+ * conflict guard, idempotency key, allowed business action,
35
+ * transaction/rollback, and safe error receipt.
36
+ *
37
+ * If you skip those checks, you can reintroduce cross-tenant writes,
38
+ * lost updates, or duplicate writes. Keep handler credentials out of MCP.
39
+ *
31
40
  * Put your app-owned transaction here.
32
41
  *
33
42
  * Examples:
@@ -36,6 +36,15 @@ def writeback(request: HandlerRequest, authorization: str | None = Header(defaul
36
36
  "details": {"dry_run": True},
37
37
  }
38
38
 
39
+ # IMPORTANT: your app handler owns the final business write.
40
+ # Runner creates the proposal and calls your handler only after approval,
41
+ # but your handler must still enforce tenant/scope, expected-version or
42
+ # conflict guard, idempotency key, allowed business action,
43
+ # transaction/rollback, and safe error receipt.
44
+ #
45
+ # If you skip those checks, you can reintroduce cross-tenant writes,
46
+ # lost updates, or duplicate writes. Keep handler credentials out of MCP.
47
+ #
39
48
  # Put your app-owned transaction here.
40
49
  #
41
50
  # Examples:
@@ -26,6 +26,13 @@ App-owned rich writeback:
26
26
  The model never receives `execute_sql`, approval tools, commit/apply tools,
27
27
  database URLs, or write credentials.
28
28
 
29
+ > **Important:** the app handler owns the final business write. Runner creates
30
+ > the proposal and calls the handler only after approval, but the handler must
31
+ > still enforce tenant/scope checks, expected-version or conflict guards,
32
+ > idempotency keys, allowed business actions, transaction/rollback, and safe
33
+ > error receipts. If those checks are skipped, the app can reintroduce
34
+ > cross-tenant writes, lost updates, or duplicate writes.
35
+
29
36
  ## Run
30
37
 
31
38
  From the repository root:
@@ -54,6 +54,12 @@ async function shutdown() {
54
54
  }
55
55
 
56
56
  async function applyAccountCredit(job, tx) {
57
+ /*
58
+ * IMPORTANT: this app handler owns the final business write.
59
+ * The helper has already verified auth, tenant scope, expected version,
60
+ * idempotency, and transaction wrapping before this function runs. Keep that
61
+ * pattern if you replace the helper or move this logic into your app.
62
+ */
57
63
  const amountCents = Number(job.patch.credit_requested_cents);
58
64
  const reason = String(job.patch.credit_reason || "approved account credit");
59
65
  if (!Number.isInteger(amountCents) || amountCents <= 0) {
@@ -113,13 +119,5 @@ function writeJson(response, statusCode, body) {
113
119
  }
114
120
 
115
121
  async function loadHandlerHelper() {
116
- try {
117
- return await import("@synapsor/handler");
118
- } catch (workspaceError) {
119
- try {
120
- return await import(new URL("./synapsor-handler.mjs", import.meta.url));
121
- } catch {
122
- throw workspaceError;
123
- }
124
- }
122
+ return await import(new URL("./synapsor-handler.mjs", import.meta.url));
125
123
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synapsor/runner",
3
- "version": "0.1.0-alpha.15",
3
+ "version": "0.1.0-alpha.17",
4
4
  "description": "Commit-safe MCP runner for Postgres and MySQL agents",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",