@synapsor/runner 1.1.1 → 1.4.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.
Files changed (72) hide show
  1. package/CHANGELOG.md +123 -1
  2. package/README.md +52 -31
  3. package/SECURITY.md +11 -0
  4. package/THREAT_MODEL.md +7 -4
  5. package/dist/cli.d.ts +5 -1
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/local-ui.d.ts.map +1 -1
  8. package/dist/runner.mjs +8103 -2601
  9. package/docs/README.md +23 -3
  10. package/docs/benchmarks/bounded-set-local.md +27 -0
  11. package/docs/bounded-set-writeback.md +298 -0
  12. package/docs/capability-authoring.md +26 -8
  13. package/docs/conformance.md +12 -0
  14. package/docs/current-scope.md +12 -4
  15. package/docs/doctor.md +21 -7
  16. package/docs/dsl-reference.md +91 -5
  17. package/docs/getting-started-own-database.md +26 -3
  18. package/docs/guarded-crud-writeback.md +218 -0
  19. package/docs/limitations.md +30 -9
  20. package/docs/oss-vs-cloud.md +1 -1
  21. package/docs/production.md +52 -20
  22. package/docs/release-notes.md +105 -2
  23. package/docs/release-policy.md +1 -1
  24. package/docs/result-envelope-v2.md +20 -6
  25. package/docs/result-envelope-v3.md +43 -0
  26. package/docs/result-envelope-v4.md +47 -0
  27. package/docs/reversible-change-sets.md +151 -0
  28. package/docs/rfcs/004-guarded-crud-receipt-authority.md +165 -0
  29. package/docs/rfcs/005-bounded-set-writeback.md +164 -0
  30. package/docs/rfcs/006-reviewed-reversible-change-sets.md +152 -0
  31. package/docs/runner-config-reference.md +83 -9
  32. package/docs/running-a-runner-fleet.md +2 -1
  33. package/docs/security-boundary.md +36 -9
  34. package/docs/why-synapsor-vs-app-guardrails.md +169 -0
  35. package/docs/writeback-executors.md +12 -11
  36. package/examples/runner-fleet/seed/mysql.sql +47 -0
  37. package/examples/runner-fleet/seed/postgres.sql +48 -0
  38. package/examples/support-plan-credit/README.md +31 -0
  39. package/examples/support-plan-credit/contract.synapsor.sql +26 -0
  40. package/examples/support-plan-credit/expected-output/tools-preview.txt +1 -0
  41. package/examples/support-plan-credit/seed/001_seed.sql +14 -1
  42. package/examples/support-plan-credit/synapsor.contract.json +101 -1
  43. package/examples/support-plan-credit/synapsor.runner.json +7 -1
  44. package/fixtures/protocol/MANIFEST.json +131 -1
  45. package/fixtures/protocol/change-set.bounded-update.v3.json +57 -0
  46. package/fixtures/protocol/change-set.delete.v2.json +31 -0
  47. package/fixtures/protocol/change-set.insert.v2.json +36 -0
  48. package/fixtures/protocol/change-set.update.v2.json +32 -0
  49. package/fixtures/protocol/compensation-change-set.update.v1.json +40 -0
  50. package/fixtures/protocol/execution-receipt.bounded-update-applied.v3.json +39 -0
  51. package/fixtures/protocol/execution-receipt.compensation-update-applied.v4.json +37 -0
  52. package/fixtures/protocol/execution-receipt.delete-applied.v2.json +25 -0
  53. package/fixtures/protocol/execution-receipt.insert-applied.v2.json +24 -0
  54. package/fixtures/protocol/execution-receipt.reconciliation-required.v2.json +25 -0
  55. package/fixtures/protocol/execution-receipt.update-applied.v2.json +25 -0
  56. package/fixtures/protocol/writeback-job.bounded-update.v3.json +43 -0
  57. package/fixtures/protocol/writeback-job.compensation-update.v4.json +34 -0
  58. package/fixtures/protocol/writeback-job.delete.v2.json +22 -0
  59. package/fixtures/protocol/writeback-job.insert.v2.json +28 -0
  60. package/fixtures/protocol/writeback-job.update.v2.json +24 -0
  61. package/package.json +2 -1
  62. package/schemas/change-set.v2.schema.json +245 -0
  63. package/schemas/change-set.v3.schema.json +112 -0
  64. package/schemas/compensation-change-set.v1.schema.json +32 -0
  65. package/schemas/execution-receipt.v2.schema.json +106 -0
  66. package/schemas/execution-receipt.v3.schema.json +40 -0
  67. package/schemas/execution-receipt.v4.schema.json +22 -0
  68. package/schemas/inverse-descriptor.v1.schema.json +46 -0
  69. package/schemas/synapsor.runner.schema.json +132 -2
  70. package/schemas/writeback-job.v2.schema.json +238 -0
  71. package/schemas/writeback-job.v3.schema.json +53 -0
  72. package/schemas/writeback-job.v4.schema.json +17 -0
package/docs/README.md CHANGED
@@ -24,6 +24,10 @@ no-database demo, wire your database, then read deeper concepts.
24
24
  ## 02 Why Raw SQL Is Dangerous
25
25
 
26
26
  - [Security Boundary](security-boundary.md): what the model can and cannot see.
27
+ - [Why Synapsor Over Prompt And Application
28
+ Guardrails](why-synapsor-vs-app-guardrails.md): where SQL authority lives,
29
+ what a hand-built semantic tool already gets right, and when the shared
30
+ contract, approval, receipt, and replay layer is worth adopting.
27
31
  - [MCP Audit](mcp-audit.md): static review for risky database MCP tools such as
28
32
  `execute_sql`, broad query tools, model-controlled tenant filters, or
29
33
  model-facing approval/commit tools.
@@ -45,6 +49,14 @@ no-database demo, wire your database, then read deeper concepts.
45
49
  links to the canonical own-database guide.
46
50
  - [Doctor](doctor.md): redacted setup checks, handler probes, direct SQL
47
51
  writeback probes, and receipt-table guidance.
52
+ - [Guarded Single-Row CRUD Writeback](guarded-crud-writeback.md): native
53
+ INSERT/UPDATE/DELETE, receipt modes, crash semantics, privileges, and
54
+ reconciliation.
55
+ - [Bounded Set Writeback](bounded-set-writeback.md): fixed-predicate set
56
+ UPDATE/DELETE, exact-review batch INSERT, mandatory caps, atomicity, and
57
+ exact receipts.
58
+ - [Reviewed Reversible Change Sets](reversible-change-sets.md): opt-in bounded
59
+ inverse capture and the separate operator proposal/approval/apply flow.
48
60
 
49
61
  ## 05 Generate Capabilities
50
62
 
@@ -89,6 +101,11 @@ no-database demo, wire your database, then read deeper concepts.
89
101
 
90
102
  - [Result Envelope v2](result-envelope-v2.md): stable
91
103
  `ok`/`summary`/`data`/`proposal`/`error` MCP tool results.
104
+ - [Result Envelope v3](result-envelope-v3.md): frozen-set jobs and receipts
105
+ with every bounded member identity and safe digest.
106
+ - [Compensation Protocol v4](result-envelope-v4.md): reviewed inverse
107
+ descriptors, compensation proposals/jobs/receipts, lineage, and
108
+ reconciliation semantics.
92
109
  - [Store Lifecycle](store-lifecycle.md): active-store leases, prune safety,
93
110
  deleted-store behavior, and concurrent server guardrails.
94
111
  - `synapsor-runner activity search`, `evidence`, `query-audit`, `receipts`,
@@ -130,7 +147,10 @@ no-database demo, wire your database, then read deeper concepts.
130
147
  - RFC source context:
131
148
  [001 result envelope](rfcs/001-result-envelope-v2.md),
132
149
  [002 handler helper](rfcs/002-app-owned-handler-helper.md),
133
- [003 integrator teardown](rfcs/003-integrator-feedback-teardown.md).
150
+ [003 integrator teardown](rfcs/003-integrator-feedback-teardown.md),
151
+ [004 guarded CRUD and receipt authority](rfcs/004-guarded-crud-receipt-authority.md),
152
+ [005 bounded set writeback](rfcs/005-bounded-set-writeback.md), and
153
+ [006 reviewed reversible change sets](rfcs/006-reviewed-reversible-change-sets.md).
134
154
 
135
155
  The public docs intentionally stay task-first. Historical implementation
136
156
  reports, release checklists, and internal planning notes are not part of the
@@ -144,8 +164,8 @@ getting-started path.
144
164
  database.
145
165
  - Writeback: execution of an approved proposal outside the model-facing MCP
146
166
  surface.
147
- - Executor: Runner's guarded one-row updater or an app-owned handler for richer
148
- approved business transactions.
167
+ - Executor: Runner's guarded one-row/bounded-set database adapter or an
168
+ app-owned handler for richer approved business transactions.
149
169
 
150
170
  ## Repository Map
151
171
 
@@ -0,0 +1,27 @@
1
+ # Bounded Set Local Measurements
2
+
3
+ These measurements are test evidence, not throughput claims or capacity
4
+ guidance. They record one run of `corepack pnpm test:bounded-set` on 2026-07-13
5
+ using synthetic rows and disposable local Docker databases.
6
+
7
+ Environment:
8
+
9
+ - Linux laptop, Intel Core i7-13800H, 20 logical CPUs, 30 GiB RAM;
10
+ - Node.js 22.22.2 and pnpm 10.14.0;
11
+ - Docker 29.5.2;
12
+ - PostgreSQL 16 and MySQL 8 containers;
13
+ - source-database receipt authority with an administrator-precreated receipt
14
+ table;
15
+ - one sequential set UPDATE per measurement, including connection setup,
16
+ source receipt, lock/preflight, transaction, and exact receipt generation.
17
+
18
+ | Engine | 1 row | 10 rows | 100 rows |
19
+ | --- | ---: | ---: | ---: |
20
+ | PostgreSQL | 30.42 ms | 53.88 ms | 154.90 ms |
21
+ | MySQL | 25.30 ms | 53.91 ms | 163.20 ms |
22
+
23
+ These figures are not portable across hardware, network topology, source
24
+ indexes, lock contention, triggers, database configuration, or receipt mode.
25
+ The claim supported by this gate is only that execution stays bounded and the
26
+ hard 100-row ceiling is exercised on both adapters. Operators must benchmark
27
+ their own reviewed contracts and database topology.
@@ -0,0 +1,298 @@
1
+ # Bounded Set Writeback
2
+
3
+ Synapsor Runner can apply a deliberately narrow class of reviewed
4
+ multi-row database changes to PostgreSQL and MySQL:
5
+
6
+ - fixed-predicate set `UPDATE`;
7
+ - fixed-predicate set `DELETE`;
8
+ - exact-review batch `INSERT`.
9
+
10
+ This is not model-generated SQL and it is not a generic batch API. The model
11
+ can ask to run a reviewed business action, but it cannot choose a table,
12
+ column, operator, ordering, predicate, tenant, or unbounded set.
13
+
14
+ ## Safety invariant
15
+
16
+ The worst action available to a prompt-injected model is to request one
17
+ contract-authored rule, against the trusted tenant, up to the reviewer-authored
18
+ row and value caps. Before approval, Runner freezes the exact ordered members.
19
+ Apply later touches only those members in one transaction and records every
20
+ identity and bounded digest.
21
+
22
+ Every bounded set requires:
23
+
24
+ 1. a fixed typed selection for existing-row UPDATE/DELETE, or the complete
25
+ reviewed item array for batch INSERT;
26
+ 2. `MAX ROWS n`, where `1 <= n <= 100`;
27
+ 3. at least one aggregate `MAX TOTAL` bound;
28
+ 4. trusted tenant binding;
29
+ 5. exact version guards and integer advancement for set UPDATE;
30
+ 6. source-unique per-item identities for batch INSERT;
31
+ 7. human/operator approval outside MCP;
32
+ 8. one atomic source transaction and an exact affected-row count;
33
+ 9. source or Runner-ledger receipts with the documented crash semantics.
34
+
35
+ A row cap is a rejection threshold, never `LIMIT n` truncation. Proposal reads
36
+ fetch at most `n + 1`; finding the extra row rejects before proposal
37
+ persistence. Aggregate overflow also rejects before persistence.
38
+
39
+ ## DSL examples
40
+
41
+ Fixed set UPDATE:
42
+
43
+ ```sql
44
+ PROPOSE ACTION close_overdue UPDATE SET
45
+ SELECT WHERE status = 'overdue'
46
+ MAX ROWS 10
47
+ MAX TOTAL balance_cents BEFORE 50000
48
+ ALLOW WRITE status
49
+ PATCH status = 'closed'
50
+ ADVANCE VERSION version USING INTEGER INCREMENT
51
+ APPROVAL ROLE billing_reviewer
52
+ WRITEBACK DIRECT SQL
53
+ ```
54
+
55
+ The selection is a contract literal. It is not an argument and is never taken
56
+ from the model. The first release supports equality terms joined by `AND`.
57
+
58
+ Exact-review batch INSERT:
59
+
60
+ ```sql
61
+ ARG items ROWS MAX 10 REQUIRED
62
+ ITEM FIELD items.id STRING REQUIRED MAX LENGTH 128
63
+ ITEM FIELD items.external_id STRING REQUIRED MAX LENGTH 128
64
+ ITEM FIELD items.amount_cents NUMBER REQUIRED MIN 1 MAX 2500
65
+ ITEM FIELD items.reason STRING REQUIRED MAX LENGTH 500
66
+
67
+ PROPOSE ACTION create_credits INSERT SET
68
+ BATCH ITEMS FROM ARG items
69
+ MAX ROWS 10
70
+ MAX TOTAL amount_cents AFTER 25000
71
+ DEDUP KEY tenant_id = TRUSTED TENANT, id = ITEM id, external_id = ITEM external_id
72
+ ALLOW WRITE amount_cents, reason
73
+ PATCH amount_cents = ITEM amount_cents
74
+ PATCH reason = ITEM reason
75
+ APPROVAL ROLE billing_reviewer
76
+ WRITEBACK DIRECT SQL
77
+ ```
78
+
79
+ The reviewer sees every allowlisted candidate item before approval. The source
80
+ must enforce the declared identity with a primary or unique constraint.
81
+
82
+ Set DELETE uses `DELETE SET`, a fixed selection, `MAX ROWS`, `MAX TOTAL`, and
83
+ an exact conflict guard. It has no patch. Runner rejects hard DELETE if it
84
+ cannot prove trigger/FK visibility or detects write triggers or widening
85
+ cascades. Prefer a set UPDATE of `deleted_at` or status when possible.
86
+
87
+ ## Worked contract-to-apply example
88
+
89
+ This PostgreSQL example closes two overdue tickets. MySQL is supported with
90
+ the same contract semantics; change the source engine and schema/database name
91
+ to match the inspected MySQL object.
92
+
93
+ Create the fixture with an administrator account, then give the Runner reader
94
+ `SELECT` and its separate writer only `SELECT, UPDATE` on this table:
95
+
96
+ ```sql
97
+ CREATE TABLE public.service_tickets (
98
+ id bigint PRIMARY KEY,
99
+ tenant_id text NOT NULL,
100
+ status text NOT NULL,
101
+ cost_cents integer NOT NULL,
102
+ version integer NOT NULL
103
+ );
104
+
105
+ INSERT INTO public.service_tickets
106
+ (id, tenant_id, status, cost_cents, version)
107
+ VALUES
108
+ (3, 'acme', 'overdue', 8000, 1),
109
+ (4, 'acme', 'overdue', 15000, 1),
110
+ (99, 'globex', 'overdue', 49000, 1);
111
+ ```
112
+
113
+ Save this as `tickets.synapsor.sql`:
114
+
115
+ ```sql
116
+ CREATE AGENT CONTEXT local_operator
117
+ BIND tenant_id FROM ENVIRONMENT SYNAPSOR_TENANT_ID REQUIRED
118
+ BIND principal FROM ENVIRONMENT SYNAPSOR_PRINCIPAL REQUIRED
119
+ TENANT BINDING tenant_id
120
+ PRINCIPAL BINDING principal
121
+ END
122
+
123
+ CREATE CAPABILITY tickets.close_overdue
124
+ USING CONTEXT local_operator
125
+ SOURCE local_db
126
+ ON public.service_tickets
127
+ PRIMARY KEY id
128
+ TENANT KEY tenant_id
129
+ CONFLICT GUARD version
130
+ LOOKUP reason BY id
131
+ ARG reason STRING REQUIRED MAX LENGTH 100
132
+ ALLOW READ id, tenant_id, status, cost_cents, version
133
+ REQUIRE EVIDENCE
134
+ PROPOSE ACTION close_overdue UPDATE SET
135
+ SELECT WHERE status = 'overdue'
136
+ MAX ROWS 10
137
+ MAX TOTAL cost_cents BEFORE 50000
138
+ ALLOW WRITE status
139
+ PATCH status = 'closed'
140
+ ADVANCE VERSION version USING INTEGER INCREMENT
141
+ APPROVAL ROLE ops_manager
142
+ WRITEBACK DIRECT SQL
143
+ END
144
+ ```
145
+
146
+ Compile the contract and create `synapsor.runner.json`:
147
+
148
+ ```bash
149
+ synapsor-runner dsl compile ./tickets.synapsor.sql \
150
+ --out ./synapsor.contract.json --strict
151
+ synapsor-runner contract validate ./synapsor.contract.json
152
+ ```
153
+
154
+ ```json
155
+ {
156
+ "version": 1,
157
+ "mode": "review",
158
+ "storage": { "sqlite_path": "./.synapsor/local.db" },
159
+ "sources": {
160
+ "local_db": {
161
+ "engine": "postgres",
162
+ "read_url_env": "SYNAPSOR_DATABASE_READ_URL",
163
+ "write_url_env": "SYNAPSOR_DATABASE_WRITE_URL",
164
+ "statement_timeout_ms": 3000,
165
+ "receipts": { "authority": "runner_ledger" }
166
+ }
167
+ },
168
+ "contracts": ["./synapsor.contract.json"]
169
+ }
170
+ ```
171
+
172
+ Load database URLs from your shell or secret manager. The config stores only
173
+ environment-variable names. Trusted tenant/principal values are process-owned,
174
+ not tool arguments:
175
+
176
+ ```bash
177
+ export SYNAPSOR_TENANT_ID=acme
178
+ export SYNAPSOR_PRINCIPAL=local_operator
179
+
180
+ synapsor-runner config validate --config ./synapsor.runner.json
181
+ synapsor-runner doctor --config ./synapsor.runner.json --check-writeback
182
+ synapsor-runner propose tickets.close_overdue \
183
+ --json '{"reason":"reviewed overdue-ticket close"}' \
184
+ --config ./synapsor.runner.json --store ./.synapsor/local.db
185
+ synapsor-runner proposals show latest --store ./.synapsor/local.db --details
186
+ ```
187
+
188
+ At this point IDs `3` and `4` are frozen in the proposal, but both source rows
189
+ are still `overdue` at version `1`; the `globex` row is outside trusted tenant
190
+ scope. Approval and apply remain operator-side commands and are not MCP tools:
191
+
192
+ ```bash
193
+ synapsor-runner proposals approve latest --actor ops_manager --yes \
194
+ --store ./.synapsor/local.db
195
+ synapsor-runner apply latest --config ./synapsor.runner.json \
196
+ --store ./.synapsor/local.db
197
+ synapsor-runner receipts list --proposal <proposal_id> \
198
+ --store ./.synapsor/local.db
199
+ synapsor-runner replay show --proposal <proposal_id> --details \
200
+ --store ./.synapsor/local.db
201
+ ```
202
+
203
+ Exactly IDs `3` and `4` become `closed` at version `2` in one transaction.
204
+ The receipt/replay list both safe primary-key identities and their bounded
205
+ digests. ID `99` is unchanged.
206
+
207
+ To test drift on a freshly seeded copy, create the proposal, then use a
208
+ database-operator session to increment one frozen row's version before
209
+ approval. `apply` returns `SET_DRIFT_CONFLICT`; the other frozen row remains
210
+ unchanged because no partial set mutation is committed:
211
+
212
+ ```sql
213
+ UPDATE public.service_tickets SET version = version + 1 WHERE id = 4;
214
+ ```
215
+
216
+ The example uses `runner_ledger`, which creates no receipt table in the source
217
+ database. For one local process, the local store is the authority; a fleet must
218
+ use authoritative shared-Postgres `runtime_store`. `source_db` authority
219
+ instead writes the mutation and receipt atomically in one source transaction
220
+ and requires its fixed precreated or auto-migrated receipt table. See
221
+ [Guarded CRUD receipt authority](guarded-crud-writeback.md).
222
+
223
+ ## Proposal and apply behavior
224
+
225
+ Proposal creation stores:
226
+
227
+ - the exact ordered primary keys and trusted tenant;
228
+ - every exact expected version;
229
+ - allowlisted before/after values or bounded digests;
230
+ - reviewed row count and aggregate values;
231
+ - a digest over the complete frozen set;
232
+ - pending human/operator approval.
233
+
234
+ Member and set digests use SHA-256 over JSON whose object keys are sorted
235
+ recursively. Array order remains significant because it is the reviewed,
236
+ deterministic primary-key order. JSON strings, finite numbers, booleans, and
237
+ null retain their protocol representation; unsupported runtime objects and
238
+ non-finite values are rejected rather than stringified implicitly. Runner
239
+ 1.4.1 also verifies the narrowly known deterministic raw-object order emitted
240
+ by 1.4.0, reconstructed from the complete stored reviewed data, so unchanged
241
+ 1.4.0 proposals remain applyable without weakening the drift checks.
242
+
243
+ Apply locks the frozen rows in deterministic primary-key order, rechecks every
244
+ reviewed value/version, performs every member mutation in one transaction, and
245
+ requires the affected count to equal the frozen count. One missing/stale row or
246
+ one database error rolls back the whole set.
247
+
248
+ Set `sources.<name>.statement_timeout_ms` to bound source waits. PostgreSQL
249
+ uses transaction-local statement and lock timeouts. MySQL applies the value to
250
+ read/preflight execution and rounds it up to whole seconds for InnoDB lock
251
+ waits; MySQL does not offer the same general DML statement timeout as
252
+ PostgreSQL.
253
+
254
+ The protocol-v3 receipt and replay include every primary key plus the bounded
255
+ before/after or tombstone digests. Kept-out fields are never added merely to
256
+ support a set operation.
257
+
258
+ ## Receipt authority and ambiguity
259
+
260
+ `source_db` authority commits the mutation and receipt in the same source
261
+ transaction. It gives the strongest retry classification.
262
+
263
+ `runner_ledger` authority creates no source receipt table, but no atomic
264
+ transaction spans the source and Runner ledger. A process loss after source
265
+ commit can therefore produce `reconciliation_required`. Runner does not retry
266
+ or guess. The operator reconciliation path re-inspects only the frozen,
267
+ allowlisted identities and records an immutable decision.
268
+
269
+ ## Explicit boundary
270
+
271
+ Use an [app-owned executor](writeback-executors.md) for:
272
+
273
+ - model-supplied or free-form predicates;
274
+ - ranges, wildcards, ordering, subqueries, or dynamic identifiers;
275
+ - more than 100 rows or any unbounded batch;
276
+ - UPSERT/MERGE, DDL, stored procedures, or cross-table/database transactions;
277
+ - external API calls, events, files, emails, payments, or other side effects;
278
+ - a hard DELETE whose trigger/cascade effects cannot be proven bounded.
279
+
280
+ Policy auto-approval is not supported for bounded sets. Every set proposal
281
+ requires a verified human/operator decision.
282
+
283
+ ## Verification
284
+
285
+ Run the disposable-engine gate:
286
+
287
+ ```bash
288
+ corepack pnpm test:bounded-set
289
+ ```
290
+
291
+ It verifies both PostgreSQL and MySQL: the exact DSL/contract path under both
292
+ receipt authorities; cap+1 and aggregate rejection before persistence; exact
293
+ UPDATE/DELETE; batch INSERT; idempotent retry; independent version, predicate,
294
+ aggregate, writable-value, missing-member, and tenant drift; injected mid-set
295
+ rollback; insert dedup/atomicity; delete trigger/cascade refusal; exact receipt
296
+ members; Runner-ledger reconciliation; and 1/10/100-row bounds. See the
297
+ [local benchmark note](benchmarks/bounded-set-local.md) and the normative
298
+ [safety RFC](rfcs/005-bounded-set-writeback.md).
@@ -138,6 +138,7 @@ reviewed runner JSON capabilities. Current parity:
138
138
  | proposal `approval` | `APPROVAL ROLE billing_lead` | 0.1 | Local mode records the required role; enforcement is still outside the model-facing MCP tool. |
139
139
  | proposal `approval.required_approvals` | `REQUIRE 2 APPROVALS` | 1.1 | Optional 1..10 distinct-reviewer quorum; defaults to 1. |
140
140
  | proposal `writeback` | `WRITEBACK DIRECT SQL`, `WRITEBACK APP HANDLER EXECUTOR name`, `WRITEBACK CLOUD WORKER`, `WRITEBACK NONE` | 0.1.7 | Handler URLs/tokens stay in `synapsor.runner.json`; contracts carry only the handler name. |
141
+ | proposal `reversibility` | `REVERSIBLE` | 1.4 | Direct SQL only. Captures a bounded inverse after unambiguous apply; operator `revert` creates a new independently approved proposal. |
141
142
  | evidence options | `REQUIRE EVIDENCE` | 0.1 | Detailed evidence sources/handle prefixes are not expressible in DSL yet; use embedded JSON or generated contract JSON for those. |
142
143
 
143
144
  ## Direct Runner Config Path
@@ -319,7 +320,11 @@ Runner rejects model-facing trust-scope arguments.
319
320
 
320
321
  ## Direct SQL Writeback
321
322
 
322
- Use direct SQL writeback only for simple bounded single-row `UPDATE` proposals.
323
+ Use direct SQL writeback for reviewed guarded single-row INSERT, UPDATE, or
324
+ DELETE proposals. Version 1.3 also supports the narrower [bounded set
325
+ form](bounded-set-writeback.md): fixed-predicate UPDATE/DELETE or complete-item
326
+ batch INSERT with mandatory row/value caps, a frozen exact set, human approval,
327
+ atomic apply, and exact receipts.
323
328
  Runner validates:
324
329
 
325
330
  - fixed table and column names;
@@ -331,19 +336,26 @@ Runner validates:
331
336
  - one affected row;
332
337
  - idempotency receipt.
333
338
 
334
- Runner does not expose generic SQL, model-generated SQL, DDL, INSERT, DELETE,
335
- UPSERT, or multi-row writes.
339
+ INSERT additionally requires a source-unique dedup key. DELETE requires an exact
340
+ version guard and fails closed on widening cascades or write triggers.
341
+
342
+ Runner does not expose generic/model-generated SQL, DDL, UPSERT, a model-chosen
343
+ predicate, unbounded writes, or cross-table transactions. Set writes that
344
+ cannot meet every bounded-set invariant stay on the app-owned executor path.
336
345
 
337
346
  Direct SQL writeback uses the source `write_url_env`, such as
338
- `SYNAPSOR_DATABASE_WRITE_URL`. The writer needs permission for
339
- `synapsor_writeback_receipts` or an administrator must pre-create and grant that
340
- table.
347
+ `SYNAPSOR_DATABASE_WRITE_URL`. Select atomic `source_db` receipts (precreated or
348
+ auto-migrated) or zero-source-schema `runner_ledger` receipts. Ledger authority
349
+ has an explicit post-source-commit crash window and reconciliation workflow; it
350
+ is not distributed exactly-once. See
351
+ [Guarded Single-Row CRUD Writeback](guarded-crud-writeback.md).
341
352
 
342
353
  ## App-Owned Executors
343
354
 
344
355
  Use an app-owned executor when an approved proposal needs richer business work:
345
- creating a credit row, inserting an outbox event, updating multiple app tables,
346
- or calling your own service.
356
+ inserting an outbox event, updating multiple app tables, calling your own
357
+ service, or creating a row whose source constraints cannot satisfy native
358
+ single-row INSERT guards.
347
359
 
348
360
  In DSL, keep the reviewed business contract portable and name the executor:
349
361
 
@@ -435,3 +447,9 @@ Commit authority stays outside the model:
435
447
  ```text
436
448
  human/operator approval -> guarded writeback or app-owned handler -> receipt/replay
437
449
  ```
450
+
451
+ If your application already exposes fixed parameterized functions, that is a
452
+ good foundation rather than an anti-pattern. Read [Why Synapsor Over Prompt And
453
+ Application Guardrails](why-synapsor-vs-app-guardrails.md) to decide whether
454
+ you also need Runner's shared contract, trusted context, evidence, approval,
455
+ receipt, replay, and compensation lifecycle.
@@ -29,12 +29,24 @@ Current fixture groups:
29
29
  - `auto-approval`
30
30
  - `aggregate-policy-limits`
31
31
  - `numeric-bounds`
32
+ - `bounded-set-threats`
33
+ - `reversible-change-sets`
32
34
 
33
35
  The fixture set is intentionally small in 0.1. It covers the runner-supported
34
36
  semantic surface first: trusted context, scoped reads, evidence handles,
35
37
  proposal boundaries, kept-out fields, manual approval, and replay envelopes.
36
38
  The aggregate-policy fixture additionally proves that reviewed daily ceilings
37
39
  fall back to human review atomically and record the limit that tripped.
40
+ The bounded-set fixture maps threats R1-R7 to executable expectations: fixed
41
+ selection, count/value caps, frozen-version drift checks, atomic rollback,
42
+ exact receipts, hard-delete side-effect refusal, and human approval. Adapter
43
+ tests and `corepack pnpm test:bounded-set` complete the live PostgreSQL/MySQL
44
+ matrix.
45
+ The reversible-change-set fixture proves that undo is a new reviewed
46
+ compensation proposal, not rollback: apply-time receipts capture only
47
+ allowlisted inverse data, compensation uses fresh guards and exact members,
48
+ ambiguous outcomes fail closed pending reconciliation, and hard delete reports
49
+ a specific best-effort-unavailable reason.
38
50
 
39
51
  Additional 0.1 parity coverage currently lives in tests and verification
40
52
  scripts rather than separate `cloud-push/` or `dsl-json-parity/` conformance
@@ -2,13 +2,19 @@
2
2
 
3
3
  The canonical scope page is [Current Limitations](limitations.md).
4
4
 
5
- Current `1.1` scope:
5
+ Current `1.4` scope:
6
6
 
7
7
  - local semantic MCP tools for Postgres/MySQL-backed business actions;
8
8
  - schema inspection and guided config generation;
9
9
  - trusted context from environment/session values;
10
10
  - evidence handles, query audit, proposals, receipts, and local replay;
11
- - guarded single-row `UPDATE` writeback for simple edits;
11
+ - guarded single-row `INSERT`, `UPDATE`, and `DELETE` writeback with explicit
12
+ receipt authority;
13
+ - fixed-predicate set `UPDATE`/`DELETE` and exact-review batch `INSERT`, with
14
+ mandatory row/value caps, frozen members, human approval, atomic execution,
15
+ and protocol-v3 exact receipts;
16
+ - opt-in reviewed compensation for direct SQL UPDATE, INSERT, soft-delete, and
17
+ exact frozen sets, using a separate operator proposal and protocol-v4 receipt;
12
18
  - app-owned `http_handler` and `command_handler` executors for richer approved
13
19
  business transactions;
14
20
  - stdio MCP, Streamable HTTP MCP, and a small JSON-RPC bridge.
@@ -25,9 +31,11 @@ Out of scope:
25
31
 
26
32
  - raw `execute_sql`;
27
33
  - model-generated SQL;
28
- - generic INSERT/DELETE/UPSERT/DDL;
29
- - generic multi-row SQL writeback;
34
+ - UPSERT, DDL, model-generated predicates, or unguarded INSERT/DELETE;
35
+ - model-generated, unbounded, or cross-table set writeback;
30
36
  - physical branching of external Postgres/MySQL;
37
+ - automatic rollback/time travel and inferred compensation for app-owned or
38
+ external effects;
31
39
  - self-hosted Synapsor Cloud;
32
40
  - unbounded or multi-region shared-ledger scale and a managed Runner fleet;
33
41
  - production SLA or compliance certification.
package/docs/doctor.md CHANGED
@@ -44,8 +44,9 @@ Idempotency-Key
44
44
 
45
45
  ## Direct SQL Writeback Checks
46
46
 
47
- For direct `sql_update` writeback, add `--check-writeback` only after reviewing
48
- the receipt-table DDL/grants:
47
+ For direct guarded single-row CRUD or bounded-set writeback, add
48
+ `--check-writeback` after reviewing the selected receipt mode and
49
+ operation-specific grants:
49
50
 
50
51
  ```bash
51
52
  synapsor-runner doctor --config synapsor.runner.json --check-writeback
@@ -55,17 +56,23 @@ This connects with the trusted writer env var named by `write_url_env` and
55
56
  checks:
56
57
 
57
58
  - writer database connectivity;
58
- - `synapsor_writeback_receipts` permission through the adapter doctor;
59
+ - source receipt migration/permissions when authority is `source_db`;
60
+ - no source receipt DDL/DML when authority is `runner_ledger`;
59
61
  - rollback-only access to each configured proposal target table;
60
- - rollback-only update permission for configured allowed write columns.
62
+ - operation-specific version, unique/dedup, generated-column, trigger,
63
+ cascade, RLS, and DML prerequisites.
64
+
65
+ For bounded sets, the normal doctor report also identifies operation kind,
66
+ fixed selection or exact batch source, row cap, aggregate bounds, and the
67
+ mandatory human/operator approval boundary. It fails validation before any
68
+ proposal exists when those portable guards are incomplete.
61
69
 
62
70
  The target-table probe uses fixed schema/table/column identifiers from the
63
71
  reviewed config. It does not accept model SQL, user SQL, arbitrary table names,
64
72
  or arbitrary column names. It runs inside a transaction and rolls back.
65
73
 
66
- An administrator must pre-create `synapsor_writeback_receipts` and grant the
67
- steady-state writer access. The doctor probe does not execute DDL or require
68
- schema `CREATE`:
74
+ For `source_db` + `precreated`, an administrator creates the receipt table and
75
+ grants steady-state access. Doctor never executes DDL in this mode:
69
76
 
70
77
  ```bash
71
78
  synapsor-runner writeback migration --engine postgres --schema synapsor
@@ -79,6 +86,13 @@ synapsor-runner writeback migration --engine mysql --schema appdb
79
86
  synapsor-runner writeback grants --engine mysql --schema appdb --writer-role "'app_writer'@'%'"
80
87
  ```
81
88
 
89
+ Use `source_db` + `auto_migrate` only when the writer may create the fixed
90
+ receipt table. Use `runner_ledger` for no source receipt table; doctor then
91
+ checks the local/small-fleet topology and crash-reconciliation prerequisites.
92
+ See [Guarded Single-Row CRUD Writeback](guarded-crud-writeback.md).
93
+ For set-specific prerequisites, see [Bounded Set
94
+ Writeback](bounded-set-writeback.md).
95
+
82
96
  Use an app-owned `http_handler` or `command_handler` executor when your
83
97
  application should own richer business writes or receipt storage.
84
98