@synapsor/runner 0.1.0-alpha.11 → 0.1.0-alpha.14

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.
Files changed (33) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/README.md +169 -23
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/runner.mjs +855 -66
  5. package/docs/README.md +21 -0
  6. package/docs/app-owned-executors.md +5 -0
  7. package/docs/capability-authoring.md +265 -0
  8. package/docs/doctor.md +98 -0
  9. package/docs/handler-helper.md +217 -0
  10. package/docs/local-mode.md +13 -2
  11. package/docs/release-notes.md +57 -2
  12. package/docs/release-policy.md +86 -0
  13. package/docs/result-envelope-v2.md +148 -0
  14. package/docs/rfcs/001-result-envelope-v2.md +143 -0
  15. package/docs/rfcs/002-app-owned-handler-helper.md +161 -0
  16. package/docs/rfcs/003-integrator-feedback-teardown.md +97 -0
  17. package/docs/store-lifecycle.md +83 -0
  18. package/docs/writeback-executors.md +18 -0
  19. package/examples/app-owned-writeback/README.md +1 -0
  20. package/examples/mcp-postgres-billing-app-handler/README.md +7 -2
  21. package/examples/mcp-postgres-billing-app-handler/app-handler.mjs +77 -149
  22. package/examples/mcp-postgres-billing-app-handler/scripts/run-demo.sh +1 -0
  23. package/examples/mcp-postgres-billing-app-handler/synapsor-handler.mjs +437 -0
  24. package/examples/mcp-postgres-billing-app-handler/synapsor.runner.json +1 -0
  25. package/package.json +3 -1
  26. package/schemas/change-set.v1.schema.json +140 -0
  27. package/schemas/execution-receipt.v1.schema.json +34 -0
  28. package/schemas/onboarding-selection.v1.schema.json +125 -0
  29. package/schemas/runner-registration.v1.schema.json +48 -0
  30. package/schemas/synapsor.app-handler-receipt.v1.json +39 -0
  31. package/schemas/synapsor.app-handler-request.v1.json +119 -0
  32. package/schemas/synapsor.runner.schema.json +412 -0
  33. package/schemas/writeback-job.v1.schema.json +121 -0
package/docs/README.md CHANGED
@@ -16,6 +16,18 @@ detail.
16
16
  service for app/server agents.
17
17
  - [OpenAI Agents SDK](openai-agents-sdk.md): use Streamable HTTP MCP with
18
18
  OpenAI-safe tool aliases.
19
+ - [Capability Authoring](capability-authoring.md): define read/proposal
20
+ capabilities, model-facing descriptions, result envelopes, trusted context,
21
+ and writeback guards. JSON Schema:
22
+ `../schemas/synapsor.runner.schema.json`.
23
+ - [Result Envelope v2](result-envelope-v2.md): the opt-in
24
+ `ok`/`summary`/`data`/`proposal`/`error` response shape for MCP tools.
25
+ - [Handler Helper](handler-helper.md): TypeScript helper for safe app-owned
26
+ rich-write handlers.
27
+ - RFC source context:
28
+ [001 result envelope](rfcs/001-result-envelope-v2.md),
29
+ [002 handler helper](rfcs/002-app-owned-handler-helper.md),
30
+ [003 integrator teardown](rfcs/003-integrator-feedback-teardown.md).
19
31
 
20
32
  ## Safety And Operations
21
33
 
@@ -25,14 +37,20 @@ detail.
25
37
  - [Cloud Mode](cloud-mode.md): what stays local and what Cloud-linked mode adds.
26
38
  - [Release Notes](release-notes.md): alpha behavior, breaking changes, and the
27
39
  stable release policy.
40
+ - [Release Policy](release-policy.md): alpha expectations, stable gates,
41
+ result envelope migration, and publish verification.
28
42
  - [Licensing](licensing.md): Apache-2.0 scope, trademark boundary, and what is
29
43
  not included in this runner repo.
30
44
  - [Dependency License Inventory](dependency-license-inventory.md): current
31
45
  dependency license summary for release review.
32
46
  - [Troubleshooting First Run](troubleshooting-first-run.md): common setup
33
47
  failures and fixes.
48
+ - [Doctor](doctor.md): redacted setup checks, handler probes, direct SQL
49
+ writeback probes, and receipt-table guidance.
34
50
  - [Local Mode](local-mode.md): local store, proposals, approval, replay, and
35
51
  writeback flow.
52
+ - [Store Lifecycle](store-lifecycle.md): active-store leases, prune safety,
53
+ deleted-store behavior, and concurrent server guardrails.
36
54
 
37
55
  ## Features
38
56
 
@@ -42,6 +60,9 @@ detail.
42
60
  for approved proposals.
43
61
  - [App-Owned Executors](app-owned-executors.md): short entry point for rich
44
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
65
+ `writeback_conflict`.
45
66
 
46
67
  Useful examples:
47
68
 
@@ -19,3 +19,8 @@ the receipt, and includes the result in replay.
19
19
 
20
20
  Do not use generic SQL for rich business transactions. Let the model propose,
21
21
  let Synapsor Runner approve/replay, and let your app execute the transaction.
22
+
23
+ For TypeScript services, prefer the first-party helper in `packages/handler`.
24
+ It enforces bearer/HMAC auth, tenant scope, expected-version guards,
25
+ idempotency, transaction rollback, and safe receipt formatting around your
26
+ business effect. See [Handler Helper](handler-helper.md).
@@ -0,0 +1,265 @@
1
+ # Capability Authoring
2
+
3
+ Use `synapsor.runner.json` to define the database actions an MCP client can see.
4
+ The model sees semantic capabilities such as `billing.inspect_invoice`, not raw
5
+ SQL, table names, write credentials, approval tools, or commit tools.
6
+
7
+ For editor validation, use the JSON Schema:
8
+
9
+ ```text
10
+ schemas/synapsor.runner.schema.json
11
+ ```
12
+
13
+ ## Minimal Shape
14
+
15
+ ```json
16
+ {
17
+ "version": 1,
18
+ "mode": "review",
19
+ "result_format": 2,
20
+ "storage": { "sqlite_path": "./.synapsor/local.db" },
21
+ "sources": {
22
+ "app_postgres": {
23
+ "engine": "postgres",
24
+ "read_url_env": "DATABASE_URL",
25
+ "write_url_env": "SYNAPSOR_DATABASE_WRITE_URL",
26
+ "statement_timeout_ms": 3000
27
+ }
28
+ },
29
+ "trusted_context": {
30
+ "provider": "environment",
31
+ "values": {
32
+ "tenant_id_env": "SYNAPSOR_TENANT_ID",
33
+ "principal_env": "SYNAPSOR_PRINCIPAL"
34
+ }
35
+ },
36
+ "capabilities": []
37
+ }
38
+ ```
39
+
40
+ `result_format: 2` makes every MCP tool call return one envelope:
41
+
42
+ ```json
43
+ {
44
+ "ok": true,
45
+ "summary": "Created proposal wrp_123. Source database changed: no.",
46
+ "action": "billing.propose_late_fee_waiver",
47
+ "kind": "proposal",
48
+ "data": null,
49
+ "proposal": {},
50
+ "error": null,
51
+ "evidence": {
52
+ "bundle_id": "ev_123",
53
+ "note": "audit/replay handle; you do not need to act on it during this turn"
54
+ },
55
+ "source_database_changed": false,
56
+ "_meta": {
57
+ "canonical_capability": "billing.propose_late_fee_waiver"
58
+ }
59
+ }
60
+ ```
61
+
62
+ Use `--result-format v2` on `mcp serve` or `mcp serve-streamable-http` if you
63
+ want to opt in from the command line instead of config.
64
+
65
+ ## Read Capability
66
+
67
+ Read capabilities inspect one scoped row or view and save evidence/query-audit
68
+ records locally.
69
+
70
+ ```json
71
+ {
72
+ "name": "billing.inspect_invoice",
73
+ "kind": "read",
74
+ "description": "Inspect one invoice in the trusted tenant before proposing a waiver or credit.",
75
+ "returns_hint": "Returns invoice amount, late fee, status, policy facts, and an audit evidence handle.",
76
+ "source": "app_postgres",
77
+ "target": {
78
+ "schema": "public",
79
+ "table": "invoices",
80
+ "primary_key": "id",
81
+ "tenant_key": "tenant_id"
82
+ },
83
+ "args": {
84
+ "invoice_id": {
85
+ "type": "string",
86
+ "required": true,
87
+ "max_length": 128,
88
+ "description": "Invoice id, e.g. INV-3001."
89
+ }
90
+ },
91
+ "lookup": { "id_from_arg": "invoice_id" },
92
+ "visible_columns": ["id", "tenant_id", "status", "late_fee_cents", "updated_at"],
93
+ "evidence": "required",
94
+ "max_rows": 1
95
+ }
96
+ ```
97
+
98
+ Model-facing descriptions matter. They should explain when to use the tool and
99
+ what the result contains. Runner also adds evidence-handle guidance so the model
100
+ does not waste a turn trying to call an audit handle.
101
+
102
+ ## Proposal Capability
103
+
104
+ Proposal capabilities create an exact before/after diff. They do not mutate your
105
+ source database. Approval and writeback stay outside the model-facing MCP tool
106
+ surface.
107
+
108
+ ```json
109
+ {
110
+ "name": "billing.propose_late_fee_waiver",
111
+ "kind": "proposal",
112
+ "description": "Propose waiving one invoice late fee after inspecting invoice and policy evidence.",
113
+ "returns_hint": "Returns a review-required proposal id, exact field diff, evidence handle, and source_database_changed:false.",
114
+ "source": "app_postgres",
115
+ "target": {
116
+ "schema": "public",
117
+ "table": "invoices",
118
+ "primary_key": "id",
119
+ "tenant_key": "tenant_id"
120
+ },
121
+ "args": {
122
+ "invoice_id": {
123
+ "type": "string",
124
+ "required": true,
125
+ "description": "Invoice id, e.g. INV-3001."
126
+ },
127
+ "reason": {
128
+ "type": "string",
129
+ "required": true,
130
+ "max_length": 500,
131
+ "description": "Business reason for the proposed waiver."
132
+ }
133
+ },
134
+ "lookup": { "id_from_arg": "invoice_id" },
135
+ "visible_columns": ["id", "tenant_id", "status", "late_fee_cents", "waiver_reason", "updated_at"],
136
+ "patch": {
137
+ "late_fee_cents": { "fixed": 0 },
138
+ "waiver_reason": { "from_arg": "reason" }
139
+ },
140
+ "allowed_columns": ["late_fee_cents", "waiver_reason"],
141
+ "numeric_bounds": {
142
+ "late_fee_cents": { "minimum": 0, "maximum": 10000 }
143
+ },
144
+ "conflict_guard": { "column": "updated_at" },
145
+ "approval": { "mode": "human", "required_role": "billing_lead" }
146
+ }
147
+ ```
148
+
149
+ ## Trusted Context
150
+
151
+ Tenant, principal, approval authority, source ids, and row-version authority
152
+ must come from trusted backend/session context, not from model arguments.
153
+
154
+ Good:
155
+
156
+ ```json
157
+ "trusted_context": {
158
+ "provider": "environment",
159
+ "values": {
160
+ "tenant_id_env": "SYNAPSOR_TENANT_ID",
161
+ "principal_env": "SYNAPSOR_PRINCIPAL"
162
+ }
163
+ }
164
+ ```
165
+
166
+ Bad:
167
+
168
+ ```json
169
+ "args": {
170
+ "tenant_id": { "type": "string" }
171
+ }
172
+ ```
173
+
174
+ Runner rejects model-facing trust-scope arguments.
175
+
176
+ ## Direct SQL Writeback
177
+
178
+ Use direct SQL writeback only for simple bounded single-row `UPDATE` proposals.
179
+ Runner validates:
180
+
181
+ - fixed table and column names;
182
+ - primary-key targeting;
183
+ - tenant guard;
184
+ - `allowed_columns`;
185
+ - numeric bounds and transition guards;
186
+ - optimistic conflict guard such as `updated_at`;
187
+ - one affected row;
188
+ - idempotency receipt.
189
+
190
+ Runner does not expose generic SQL, model-generated SQL, DDL, INSERT, DELETE,
191
+ UPSERT, or multi-row writes.
192
+
193
+ Direct SQL writeback uses the source `write_url_env`, such as
194
+ `SYNAPSOR_DATABASE_WRITE_URL`. The writer needs permission for
195
+ `synapsor_writeback_receipts` or an administrator must pre-create and grant that
196
+ table.
197
+
198
+ ## App-Owned Executors
199
+
200
+ Use an app-owned executor when an approved proposal needs richer business work:
201
+ creating a credit row, inserting an outbox event, updating multiple app tables,
202
+ or calling your own service.
203
+
204
+ ```json
205
+ "executors": {
206
+ "billing_handler": {
207
+ "type": "http_handler",
208
+ "url_env": "BILLING_WRITEBACK_URL",
209
+ "method": "POST",
210
+ "auth": {
211
+ "type": "bearer_env",
212
+ "token_env": "BILLING_WRITEBACK_TOKEN"
213
+ },
214
+ "signing_secret_env": "BILLING_WRITEBACK_SIGNING_SECRET",
215
+ "timeout_ms": 5000
216
+ }
217
+ }
218
+ ```
219
+
220
+ Then reference it from a proposal capability:
221
+
222
+ ```json
223
+ {
224
+ "name": "billing.propose_account_credit",
225
+ "kind": "proposal",
226
+ "executor": "billing_handler"
227
+ }
228
+ ```
229
+
230
+ Approval still happens outside MCP. Runner sends the approved job to your
231
+ handler, and the handler returns an applied/conflict/failed receipt for replay.
232
+ See [App-Owned Executors](app-owned-executors.md) and
233
+ [Writeback Executors](writeback-executors.md).
234
+
235
+ ## OpenAI Aliases
236
+
237
+ Canonical Synapsor names use dots, such as `billing.inspect_invoice`. Some
238
+ clients require function-safe names. Use:
239
+
240
+ ```bash
241
+ synapsor-runner mcp serve-streamable-http \
242
+ --config ./synapsor.runner.json \
243
+ --store ./.synapsor/local.db \
244
+ --auth-token-env SYNAPSOR_RUNNER_HTTP_TOKEN \
245
+ --alias-mode openai
246
+ ```
247
+
248
+ The model sees aliases such as `billing__inspect_invoice`. Runner includes the
249
+ canonical name in tool metadata and descriptions so audit/replay still use the
250
+ real capability name.
251
+
252
+ ## Why Not `execute_sql`
253
+
254
+ `execute_sql(sql)` gives the model database authority. Synapsor Runner gives the
255
+ model proposal authority:
256
+
257
+ ```text
258
+ model-facing MCP tool -> trusted context -> scoped read -> evidence -> proposal
259
+ ```
260
+
261
+ Commit authority stays outside the model:
262
+
263
+ ```text
264
+ human/operator approval -> guarded writeback or app-owned handler -> receipt/replay
265
+ ```
package/docs/doctor.md ADDED
@@ -0,0 +1,98 @@
1
+ # Doctor
2
+
3
+ Use `doctor` to check a local Runner setup without printing database URLs,
4
+ passwords, bearer tokens, signing secrets, or private keys.
5
+
6
+ ```bash
7
+ synapsor-runner doctor --config synapsor.runner.json
8
+ synapsor-runner doctor --config synapsor.runner.json --json
9
+ synapsor-runner doctor --config synapsor.runner.json --report --redact --output synapsor-doctor.md
10
+ ```
11
+
12
+ The default check validates:
13
+
14
+ - config shape;
15
+ - trusted context environment variables;
16
+ - read credential environment variables;
17
+ - read/write credential separation;
18
+ - reachable source metadata when the read env var is set;
19
+ - configured target tables and columns;
20
+ - MCP tool boundary, including absence of raw SQL and commit tools;
21
+ - local store stats.
22
+
23
+ ## App-Owned Handler Checks
24
+
25
+ For `http_handler` executors, add `--check-handlers`:
26
+
27
+ ```bash
28
+ synapsor-runner doctor --config synapsor.runner.json --check-handlers
29
+ ```
30
+
31
+ This checks handler URL/token/signing-secret env vars and sends a reachability
32
+ probe to the handler endpoint. It does not apply a proposal and does not send a
33
+ writeback job.
34
+
35
+ Use `signing_secret_env` for non-loopback handler deployments so Runner signs
36
+ requests with:
37
+
38
+ ```text
39
+ X-Synapsor-Signature
40
+ X-Synapsor-Issued-At
41
+ X-Synapsor-Proposal-Id
42
+ Idempotency-Key
43
+ ```
44
+
45
+ ## Direct SQL Writeback Checks
46
+
47
+ For direct `sql_update` writeback, add `--check-writeback` only after reviewing
48
+ the receipt-table DDL/grants:
49
+
50
+ ```bash
51
+ synapsor-runner doctor --config synapsor.runner.json --check-writeback
52
+ ```
53
+
54
+ This connects with the trusted writer env var named by `write_url_env` and
55
+ checks:
56
+
57
+ - writer database connectivity;
58
+ - `synapsor_writeback_receipts` permission through the adapter doctor;
59
+ - rollback-only access to each configured proposal target table;
60
+ - rollback-only update permission for configured allowed write columns.
61
+
62
+ The target-table probe uses fixed schema/table/column identifiers from the
63
+ reviewed config. It does not accept model SQL, user SQL, arbitrary table names,
64
+ or arbitrary column names. It runs inside a transaction and rolls back.
65
+
66
+ The receipt-table probe can create `synapsor_writeback_receipts` if the writer
67
+ has permission. If your policy does not allow Runner to create tables in the
68
+ application schema, pre-create the table and grant access:
69
+
70
+ ```bash
71
+ synapsor-runner writeback migration --engine postgres --schema synapsor
72
+ synapsor-runner writeback grants --engine postgres --schema synapsor --writer-role app_writer
73
+ ```
74
+
75
+ For MySQL:
76
+
77
+ ```bash
78
+ synapsor-runner writeback migration --engine mysql --schema appdb
79
+ synapsor-runner writeback grants --engine mysql --schema appdb --writer-role "'app_writer'@'%'"
80
+ ```
81
+
82
+ Use an app-owned `http_handler` or `command_handler` executor when your
83
+ application should own richer business writes or receipt storage.
84
+
85
+ ## Redaction
86
+
87
+ Doctor output intentionally uses safe categories such as:
88
+
89
+ ```text
90
+ connection failed
91
+ authentication failed
92
+ permission denied
93
+ configured object not found
94
+ database probe failed
95
+ ```
96
+
97
+ Raw driver errors, connection strings, passwords, tokens, signing secrets, and
98
+ handler URLs are not printed in the report.
@@ -0,0 +1,217 @@
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
+ The helper is the safe-by-default path for rich writes such as:
7
+
8
+ - inserting account-credit, refund-review, ticket, or ledger rows;
9
+ - updating multiple related rows inside your app transaction;
10
+ - applying business rules that belong in your application service.
11
+
12
+ The model-facing MCP tool still creates a proposal only. A human/operator
13
+ approves outside MCP. After approval, Runner sends the structured writeback
14
+ request to your handler.
15
+
16
+ ## Scope
17
+
18
+ Current alpha scope:
19
+
20
+ - TypeScript helper in `packages/handler`;
21
+ - bearer token verification;
22
+ - optional HMAC verification over the raw request body;
23
+ - typed request parsing;
24
+ - action dispatch;
25
+ - idempotency receipt lookup;
26
+ - transaction wrapper;
27
+ - `SELECT ... FOR UPDATE` target-row lock;
28
+ - tenant guard;
29
+ - expected-version stale-row guard;
30
+ - safe applied/conflict/failed receipts;
31
+ - no raw driver errors in HTTP responses.
32
+
33
+ Python helper is planned. For now, Python handlers should follow the documented
34
+ request/receipt schema and the FastAPI template in `examples/app-owned-writeback`.
35
+
36
+ ## Distribution Status
37
+
38
+ The helper implementation exists in this source repo under `packages/handler`
39
+ and is used by the app-owned executor example and tests. It is not published as
40
+ a standalone `@synapsor/handler` npm package yet.
41
+
42
+ If you installed `@synapsor/runner` from npm, use one of these alpha paths:
43
+
44
+ - generate a starter handler with `synapsor-runner handler template ...`;
45
+ - copy from `examples/app-owned-writeback/`;
46
+ - run `examples/mcp-postgres-billing-app-handler/`, which includes a bundled
47
+ `synapsor-handler.mjs` shim inside the runner package.
48
+
49
+ The `@synapsor/handler` import below is the source-checkout API and the planned
50
+ standalone package API. Do not `npm install @synapsor/handler` until that
51
+ package is published.
52
+
53
+ ## Schemas
54
+
55
+ Published schemas:
56
+
57
+ - `schemas/synapsor.app-handler-request.v1.json`
58
+ - `schemas/synapsor.app-handler-receipt.v1.json`
59
+
60
+ The helper accepts both the new `protocol_version: "1.0"` shape and the current
61
+ Runner `schema_version: "synapsor.handler-writeback.v1"` request shape during
62
+ the alpha migration.
63
+
64
+ ## TypeScript Usage From A Source Checkout
65
+
66
+ ```ts
67
+ import { createWritebackHandler } from "@synapsor/handler";
68
+
69
+ export const handler = createWritebackHandler({
70
+ tokenEnv: "SYNAPSOR_APP_HANDLER_TOKEN",
71
+ signingSecretEnv: "SYNAPSOR_APP_HANDLER_SIGNING_SECRET",
72
+ source: {
73
+ engine: "postgres",
74
+ writeUrlEnv: "SYNAPSOR_APP_WRITE_URL",
75
+ receiptTable: { schema: "synapsor", table: "handler_receipts" }
76
+ },
77
+ capabilities: {
78
+ "support.propose_plan_credit": async (job, tx) => {
79
+ const creditId = `CR-${job.proposalId.slice(-12)}`;
80
+
81
+ await tx.insert("credits", {
82
+ id: creditId,
83
+ tenant_id: job.tenantId,
84
+ invoice_id: job.objectId,
85
+ amount_cents: Number(job.patch.credit_requested_cents),
86
+ reason: String(job.patch.credit_reason),
87
+ created_by: job.principal
88
+ });
89
+
90
+ await tx.update("invoices", {
91
+ id: job.objectId,
92
+ tenant_id: job.tenantId
93
+ }, {
94
+ credited_cents:
95
+ Number(job.row.credited_cents ?? 0) +
96
+ Number(job.patch.credit_requested_cents)
97
+ });
98
+
99
+ return {
100
+ rowsAffected: 2,
101
+ effects: [{ type: "db.insert", table: "credits", id: creditId }]
102
+ };
103
+ }
104
+ }
105
+ });
106
+ ```
107
+
108
+ Mount the returned handler at your app route, for example
109
+ `POST /synapsor/writeback`.
110
+
111
+ The handler author writes only the business effect. The helper owns the safety
112
+ loop around that effect.
113
+
114
+ ## What The Helper Enforces
115
+
116
+ The helper checks these before your business function can mutate state:
117
+
118
+ - the bearer token matches the configured environment variable;
119
+ - the optional HMAC signature is valid and fresh;
120
+ - the request protocol is supported;
121
+ - the action maps to a configured capability function;
122
+ - the target row exists inside the trusted tenant;
123
+ - the row version still matches the proposal's expected version;
124
+ - the idempotency key was not already applied.
125
+
126
+ If the row is missing or belongs to another tenant, the helper returns:
127
+
128
+ ```json
129
+ {
130
+ "status": "conflict",
131
+ "rows_affected": 0,
132
+ "source_database_mutated": false,
133
+ "safe_error_code": "ROW_NOT_FOUND_OR_WRONG_TENANT"
134
+ }
135
+ ```
136
+
137
+ If the row changed after proposal creation, the helper returns:
138
+
139
+ ```json
140
+ {
141
+ "status": "conflict",
142
+ "rows_affected": 0,
143
+ "source_database_mutated": false,
144
+ "safe_error_code": "ROW_CHANGED_AFTER_PROPOSAL"
145
+ }
146
+ ```
147
+
148
+ If your business function throws, the helper rolls back the transaction and
149
+ returns a safe failed receipt. Raw driver and exception text are not exposed to
150
+ the caller.
151
+
152
+ ## Runner-Side Signing Config
153
+
154
+ Configure the matching `http_handler` executor with the same signing-secret env
155
+ name:
156
+
157
+ ```json
158
+ {
159
+ "executors": {
160
+ "billing_handler": {
161
+ "type": "http_handler",
162
+ "url_env": "BILLING_WRITEBACK_URL",
163
+ "method": "POST",
164
+ "auth": {
165
+ "type": "bearer_env",
166
+ "token_env": "BILLING_WRITEBACK_TOKEN"
167
+ },
168
+ "signing_secret_env": "SYNAPSOR_APP_HANDLER_SIGNING_SECRET",
169
+ "timeout_ms": 5000
170
+ }
171
+ }
172
+ }
173
+ ```
174
+
175
+ When this field is set, Runner signs the exact request body and sends
176
+ `X-Synapsor-Signature`, `X-Synapsor-Issued-At`,
177
+ `X-Synapsor-Proposal-Id`, and `Idempotency-Key`. The helper verifies those
178
+ headers before parsing or applying the writeback request.
179
+
180
+ ## Signing
181
+
182
+ For loopback-only development, bearer auth may be enough. For any handler that
183
+ is reachable outside the local process, enable HMAC:
184
+
185
+ ```ts
186
+ createWritebackHandler({
187
+ tokenEnv: "SYNAPSOR_APP_HANDLER_TOKEN",
188
+ signingSecretEnv: "SYNAPSOR_APP_HANDLER_SIGNING_SECRET",
189
+ // ...
190
+ });
191
+ ```
192
+
193
+ Runner sends:
194
+
195
+ ```text
196
+ Authorization: Bearer <token>
197
+ X-Synapsor-Signature: sha256=<hmac>
198
+ X-Synapsor-Issued-At: <iso timestamp>
199
+ X-Synapsor-Proposal-Id: wrp_...
200
+ Idempotency-Key: wrp_...
201
+ ```
202
+
203
+ The HMAC is computed over the raw body. The helper enforces a short issued-at
204
+ skew window.
205
+
206
+ ## Receipt Storage
207
+
208
+ The helper's Postgres adapter stores idempotency receipts in a receipt table.
209
+ Prefer a dedicated schema, for example:
210
+
211
+ ```sql
212
+ CREATE SCHEMA IF NOT EXISTS synapsor;
213
+ GRANT USAGE, CREATE ON SCHEMA synapsor TO app_writeback_user;
214
+ ```
215
+
216
+ If your application already has a receipt/idempotency table, implement the
217
+ `WritebackHandlerDatabase` interface and pass it as `database`.
@@ -273,13 +273,24 @@ or prune it without touching your source Postgres/MySQL database:
273
273
 
274
274
  ```bash
275
275
  synapsor-runner store stats --store ./.synapsor/local.db
276
+ synapsor-runner events tail --store ./.synapsor/local.db
276
277
  synapsor-runner store vacuum --store ./.synapsor/local.db
277
278
  synapsor-runner store prune --store ./.synapsor/local.db --older-than 30d --dry-run
278
279
  synapsor-runner store prune --store ./.synapsor/local.db --older-than 30d --yes
280
+ synapsor-runner store prune --store ./.synapsor/local.db --older-than 30d --yes --force
281
+ synapsor-runner store reset --store ./.synapsor/local.db --yes
279
282
  ```
280
283
 
281
- `store prune` defaults to dry-run. Use `--yes` only after reviewing the row
282
- counts it will remove.
284
+ `events tail` shows local lifecycle events already recorded in the SQLite
285
+ ledger, including proposal creation, approval/rejection, writeback jobs, and
286
+ writeback applied/conflict/failed receipts. Add `--follow` to keep polling a
287
+ running local store.
288
+
289
+ `store prune` defaults to dry-run. `store reset` requires `--yes` and removes
290
+ only the local SQLite ledger files. MCP server modes write a small active-store
291
+ lease next to the SQLite file; destructive store operations refuse while that
292
+ lease points at a live PID unless you pass `--force` after verifying the server
293
+ is stopped or stale.
283
294
 
284
295
  ## Boundary
285
296