@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
@@ -0,0 +1,164 @@
1
+ # RFC 005: Bounded Set Writeback
2
+
3
+ Status: implementation gate
4
+
5
+ ## Purpose
6
+
7
+ Runner 1.3 may execute a reviewed set write only when the set is fixed by the
8
+ contract, frozen before approval, bounded by both row count and business value,
9
+ and applied atomically. A row cap by itself is not a safety boundary.
10
+
11
+ This RFC authorizes three direct operations:
12
+
13
+ - fixed-predicate set UPDATE;
14
+ - fixed-predicate set DELETE;
15
+ - exact-review batch INSERT.
16
+
17
+ Free-form predicates, model-selected columns or operators, unbounded sets,
18
+ UPSERT, DDL, cross-table work, and external effects remain app-owned executor
19
+ work.
20
+
21
+ ## Canonical Model
22
+
23
+ Set operations are explicit. They never reuse a single-row lookup by merely
24
+ raising `max_rows`.
25
+
26
+ ```json
27
+ {
28
+ "operation": {
29
+ "kind": "update",
30
+ "cardinality": "set",
31
+ "selection": {
32
+ "all": [
33
+ { "column": "status", "operator": "eq", "value": "overdue" }
34
+ ]
35
+ },
36
+ "max_rows": 10,
37
+ "aggregate_bounds": [
38
+ { "column": "balance_cents", "measure": "before", "maximum": 50000 }
39
+ ]
40
+ }
41
+ }
42
+ ```
43
+
44
+ The first release accepts only contract literals in `selection`. The typed AST
45
+ reserves no raw SQL escape hatch. The model can trigger the reviewed capability
46
+ but cannot choose a predicate, column, operator, ordering, range, wildcard, or
47
+ set size.
48
+
49
+ Batch INSERT uses an explicitly typed bounded item-array argument. Every item
50
+ is fully materialized in the proposal, receives the trusted tenant, and has a
51
+ source-enforced unique dedup identity. A generic JSON blob is not accepted.
52
+
53
+ ## Hard Limits
54
+
55
+ - `MAX ROWS` is mandatory and must be between 1 and 100.
56
+ - at least one aggregate value bound is mandatory;
57
+ - a proposal query fetches at most `MAX ROWS + 1` rows;
58
+ - at most 64 reviewed columns may be captured per row;
59
+ - before/after payloads and digests are bounded before persistence;
60
+ - overflow rejects the proposal; it is never truncated to the cap.
61
+
62
+ The implementation ceiling of 100 is a safety limit, not a throughput claim.
63
+
64
+ ## Frozen Set
65
+
66
+ At proposal time Runner stores, in deterministic primary-key order:
67
+
68
+ - exact primary key;
69
+ - trusted tenant;
70
+ - expected version for UPDATE/DELETE;
71
+ - allowlisted before and after values;
72
+ - per-row before and after digests;
73
+ - count, aggregate measurements, and one set digest.
74
+
75
+ Apply never re-runs the broad selection predicate. It locks the frozen primary
76
+ keys in the same deterministic order, rechecks tenant and every expected
77
+ version, and mutates the complete set in one transaction. A missing or stale
78
+ member rolls back the whole transaction.
79
+
80
+ ## Approval
81
+
82
+ All set operations require verified human/operator approval in 1.3. Policy
83
+ auto-approval is rejected rather than silently downgraded. Set hard DELETE and
84
+ batch INSERT are never auto-approved. This keeps row/value-aware policy design
85
+ out of the first release.
86
+
87
+ ## Exact Effects
88
+
89
+ The receipt and replay carry every affected primary key and bounded before/
90
+ after digest. Kept-out fields are never fetched merely to improve audit detail.
91
+ The source receipt and mutation share one transaction in `source_db` mode.
92
+ `runner_ledger` mode retains the documented post-source-commit ambiguity and
93
+ uses `reconciliation_required`; it never retries an ambiguous batch.
94
+
95
+ ## Operation Rules
96
+
97
+ ### Set UPDATE
98
+
99
+ - fixed literal predicate;
100
+ - fixed or scalar-argument patch with existing per-field bounds;
101
+ - exact version guard and monotonic version advance for every row;
102
+ - mandatory aggregate bound;
103
+ - one transaction and exact affected-row count.
104
+
105
+ ### Set DELETE
106
+
107
+ - fixed literal predicate;
108
+ - exact version guard for every row;
109
+ - verified human approval;
110
+ - no write triggers or cascading foreign keys;
111
+ - bounded tombstones and exact identities.
112
+
113
+ Soft delete remains the preferred set operation because it is a guarded
114
+ UPDATE and can later be reversed safely.
115
+
116
+ ### Batch INSERT
117
+
118
+ - one typed item array with `max_items` no greater than `MAX ROWS`;
119
+ - every candidate row shown in the proposal;
120
+ - trusted tenant injected by Runner;
121
+ - source UNIQUE/PRIMARY KEY dedup identity for every item;
122
+ - per-item bounds plus a mandatory aggregate bound;
123
+ - all rows and source receipts commit atomically or none do.
124
+
125
+ ## Threat Cases And Fixtures
126
+
127
+ | Threat | Mitigation | Conformance fixture |
128
+ | --- | --- | --- |
129
+ | R1 selection abuse | literal typed predicate; no model predicate inputs | `bounded-set-r1-fixed-selection` |
130
+ | R2 misleading cap | row cap plus aggregate value bound; overflow rejects | `bounded-set-r2-count-value-cap` |
131
+ | R3 TOCTOU drift | frozen PK/version set; apply never broad-requeries | `bounded-set-r3-frozen-drift` |
132
+ | R4 partial application | one transaction; exact count; rollback on any member | `bounded-set-r4-atomic-rollback` |
133
+ | R5 audit collapse | every key and bounded before/after digest in receipt/replay | `bounded-set-r5-exact-receipt` |
134
+ | R6 DELETE cascades | hard DELETE preflight rejects cascades/write triggers | `bounded-set-r6-delete-side-effects` |
135
+ | R7 auto-approval amplification | human approval mandatory; policy mode rejected | `bounded-set-r7-human-approval` |
136
+
137
+ Each fixture must be exercised by canonical validation and runtime/adapter
138
+ tests. A fixture file that is never loaded by a test is not evidence.
139
+
140
+ ## Crash And Receipt Matrix
141
+
142
+ | Outcome | `source_db` | `runner_ledger` |
143
+ | --- | --- | --- |
144
+ | before source BEGIN | no effect | durable intent, no effect |
145
+ | one member stale | complete rollback and conflict receipt | complete rollback and terminal conflict |
146
+ | process death before source commit | complete rollback | complete rollback; intent may be safely resolved as not committed |
147
+ | process death after source commit | source mutation and receipt prove outcome atomically | `reconciliation_required`; never automatic retry |
148
+ | retry after completed receipt | `already_applied` | `already_applied` from authoritative ledger |
149
+
150
+ No mode claims a distributed transaction across Runner's ledger and an external
151
+ source database.
152
+
153
+ ## Release Gate
154
+
155
+ The feature remains unavailable until Postgres and MySQL live tests prove:
156
+
157
+ - cap+1 and aggregate overflow reject before proposal persistence;
158
+ - one stale member and injected failure leave every source row unchanged;
159
+ - lock order is deterministic;
160
+ - exact IDs/digests appear in receipts and replay;
161
+ - hard DELETE side effects and auto-approval are rejected;
162
+ - batch INSERT dedup and crash behavior are safe in both receipt modes;
163
+ - seeded 1, 10, and 100 row measurements are recorded as local evidence only.
164
+
@@ -0,0 +1,152 @@
1
+ # RFC 006: Reviewed Reversible Change Sets
2
+
3
+ Status: implemented and verified for the prepared Runner 1.4 release line
4
+
5
+ ## Problem
6
+
7
+ An applied direct database write currently has evidence, approval, a guarded
8
+ writeback receipt, and replay, but no bounded compensating action. Correcting a
9
+ mistake requires an operator to author a new forward proposal manually.
10
+
11
+ Runner should be able to prepare a reviewed compensation without becoming a
12
+ database time-travel system or introducing an unreviewed rollback path.
13
+
14
+ ## Safety boundary
15
+
16
+ Reversal is opt-in contract authority:
17
+
18
+ ```json
19
+ {
20
+ "reversibility": {
21
+ "mode": "reviewed_inverse"
22
+ }
23
+ }
24
+ ```
25
+
26
+ The declaration is valid only for Runner-owned `direct_sql` writeback. It does
27
+ not apply to app handlers, Cloud workers, email, payments, or any other
28
+ external effect.
29
+
30
+ `synapsor-runner revert <proposal-id>` creates a new proposal. It never:
31
+
32
+ - mutates the source database;
33
+ - approves the proposal;
34
+ - bypasses tenant or operator identity checks;
35
+ - appears in the model-facing MCP tool list.
36
+
37
+ The new proposal must be independently approved and applied through the normal
38
+ trusted operator path.
39
+
40
+ ## Protocol split
41
+
42
+ Published v1, v2, and v3 forward writeback jobs keep their existing meaning.
43
+ Reversible forward jobs carry an additive, explicit inverse-capture request.
44
+ The resulting execution receipt carries a bounded inverse descriptor only
45
+ after a successful, unambiguous source transaction.
46
+
47
+ Compensation is executed through a distinct normalized protocol v4 job. A v4
48
+ job contains:
49
+
50
+ - the exact source, tenant, and primary-key authority;
51
+ - a bounded, deterministically ordered member list;
52
+ - the original single-row or set cardinality;
53
+ - the state expected after the forward write;
54
+ - only the reviewed values to restore or remove;
55
+ - the original row and aggregate caps;
56
+ - the original approval role and quorum;
57
+ - lineage linking root, parent, and reverted proposals and the forward receipt;
58
+ - a maximum lineage depth.
59
+
60
+ This prevents compensation semantics from being inferred from a legacy job.
61
+
62
+ ## Operation semantics
63
+
64
+ ### UPDATE and soft delete
65
+
66
+ The inverse restores only previously reviewed writable values. The row must
67
+ still match the exact reviewed state left by the forward write. A monotonic
68
+ version column advances again during compensation; it is never decremented.
69
+ Any intervening write fails closed.
70
+
71
+ A soft delete is an UPDATE of a reviewed status or deletion marker and follows
72
+ these same rules.
73
+
74
+ ### INSERT
75
+
76
+ The inverse deletes only the exact Runner-created identity. The current row
77
+ must still match the allowlisted values inserted by Runner. Trigger and foreign
78
+ key safety is rechecked immediately before deletion. A new dependent row or a
79
+ changed value blocks compensation.
80
+
81
+ Because this delete removes a row whose complete Runner-supplied values are
82
+ known, its own inverse may reinsert that exact row. It cannot infer or restore
83
+ unreviewed database-generated or trigger-generated state.
84
+
85
+ ### Hard DELETE
86
+
87
+ A general hard DELETE records bounded reviewed before-state evidence but is
88
+ not automatically restorable. Generated identities, hidden required columns,
89
+ cascaded children, and trigger side effects cannot be reconstructed safely.
90
+ The receipt marks the inverse unavailable with specific reason codes and the
91
+ CLI recommends soft delete or an app-owned compensation capability.
92
+
93
+ ## Bounded sets
94
+
95
+ Set compensation uses exactly the original frozen identities. It cannot widen
96
+ the predicate or increase the original row/value caps. Every member is locked
97
+ in deterministic primary-key order and checked before any mutation. One stale
98
+ member rolls back the entire transaction.
99
+
100
+ The inverse descriptor is bounded to 100 members and 256 scalar fields per
101
+ member, matching the public protocol hard ceilings.
102
+
103
+ ## Receipt authority and crash behavior
104
+
105
+ With source-database receipt authority, mutation and source receipt remain in
106
+ one transaction. The inverse descriptor is deterministic from the immutable
107
+ job plus the state verified in that transaction, so an idempotent retry can
108
+ reproduce it without rereading kept-out columns.
109
+
110
+ With Runner-ledger authority, the existing cross-database crash window remains.
111
+ An ambiguous forward or compensation outcome enters
112
+ `reconciliation_required`. Runner does not generate or apply a revert until an
113
+ operator reconciles the original outcome and an applied receipt with an
114
+ available inverse exists.
115
+
116
+ ## Lineage
117
+
118
+ Every compensation proposal records:
119
+
120
+ - `root_proposal_id`;
121
+ - `parent_proposal_id`;
122
+ - `reverts_proposal_id`;
123
+ - `forward_receipt_hash`;
124
+ - `depth`.
125
+
126
+ Depth is capped at 16. The same applied receipt may have only one active or
127
+ completed compensation child. To undo a compensation, the operator reverts the
128
+ child proposal, creating a linear audited chain instead of a cycle.
129
+
130
+ ## Data minimization
131
+
132
+ Inverse capture may retain only:
133
+
134
+ - fixed primary-key and trusted tenant identity;
135
+ - conflict/version metadata;
136
+ - fields in the reviewed write allowlist;
137
+ - Runner-supplied deterministic deduplication fields needed to identify an
138
+ inserted row.
139
+
140
+ Visible read fields that were not writable and all kept-out fields are excluded.
141
+ Reversibility is rejected when the safe inverse cannot be represented with that
142
+ bounded data.
143
+
144
+ ## Explicit non-goals
145
+
146
+ - point-in-time recovery or WAL replacement;
147
+ - automatic rollback;
148
+ - model-facing revert, approve, apply, or receipt tools;
149
+ - restoration of arbitrary hard deletes;
150
+ - compensation of app-owned executors or external effects;
151
+ - cross-table, cross-database, or unbounded compensation;
152
+ - weakening the existing optimistic concurrency and receipt-authority model.
@@ -112,7 +112,13 @@ not print database URLs or create the schema.
112
112
  "read_url_env": "BILLING_POSTGRES_READ_URL",
113
113
  "write_url_env": "BILLING_POSTGRES_WRITE_URL",
114
114
  "read_only": false,
115
- "statement_timeout_ms": 3000
115
+ "statement_timeout_ms": 3000,
116
+ "receipts": {
117
+ "authority": "source_db",
118
+ "provisioning": "precreated",
119
+ "schema": "synapsor",
120
+ "table": "writeback_receipts"
121
+ }
116
122
  }
117
123
  }
118
124
  }
@@ -121,8 +127,13 @@ not print database URLs or create the schema.
121
127
  `engine` is `postgres` or `mysql`. `read_url_env` is required. Use a
122
128
  least-privilege read credential. `write_url_env` is optional unless direct SQL
123
129
  writeback must apply; it should name a separate restricted writer. `read_only:
124
- true` forbids direct SQL writeback. `statement_timeout_ms` is a positive read
125
- timeout. `ssl` carries adapter-specific reviewed SSL options when used.
130
+ true` forbids direct SQL writeback. `statement_timeout_ms` is an
131
+ operator-controlled positive timeout used for reads and direct SQL writeback.
132
+ PostgreSQL applies it as transaction-local `statement_timeout` and
133
+ `lock_timeout`. MySQL applies it to read/preflight execution and rounds it up
134
+ to whole seconds for `innodb_lock_wait_timeout`; MySQL does not provide the
135
+ same general DML statement-timeout guarantee as PostgreSQL. `ssl` carries
136
+ adapter-specific reviewed SSL options when used.
126
137
 
127
138
  A contract's `SOURCE billing_postgres` must exactly match a `sources` key.
128
139
 
@@ -131,8 +142,31 @@ Long-running servers reuse native driver pools. Optional `pool` keys are
131
142
  `idle_timeout_ms` (30000), `queue_timeout_ms` (5000), and `queue_limit`
132
143
  (default `max(10, max_connections * 4)`). Queue overflow returns
133
144
  `SOURCE_POOL_QUEUE_FULL`; acquisition timeout returns `SOURCE_POOL_TIMEOUT`.
145
+ With result envelope v2, these conditions and recognized transient driver
146
+ connection failures are model-facing `TEMPORARILY_UNAVAILABLE` errors with
147
+ `retryable: true` and a bounded `retry_after_ms`. Operational logs retain only
148
+ a normalized safe runtime code, not the driver message, host, or credentials.
134
149
  One-shot CLI commands close their pools.
135
150
 
151
+ ### Source receipts
152
+
153
+ `sources.<name>.receipts.authority` is `source_db` or `runner_ledger`.
154
+
155
+ - `source_db` requires `provisioning: "precreated"` or `"auto_migrate"`.
156
+ Optional fixed `schema` and `table` identifiers select the receipt table.
157
+ The source mutation and receipt share one transaction.
158
+ - `runner_ledger` forbids `provisioning`, `schema`, and `table`. It creates no
159
+ source receipt table. Local SQLite is permitted for one process;
160
+ networked/multi-Runner use requires authoritative shared Postgres
161
+ `runtime_store` and rejects mirror mode.
162
+
163
+ Existing configs without `receipts` retain the compatibility source-receipt
164
+ behavior. New onboarding asks explicitly. Runner-ledger UPDATE requires exact
165
+ version advancement; INSERT requires source-enforced deduplication; DELETE
166
+ requires an exact version guard. Ambiguous post-commit outcomes stop at
167
+ `reconciliation_required`. See
168
+ [Guarded Single-Row CRUD Writeback](guarded-crud-writeback.md).
169
+
136
170
  ## Trusted context
137
171
 
138
172
  ```json
@@ -227,6 +261,39 @@ fields are documented in the JSON Schema and
227
261
  lookup, visible fields, evidence, patch allowlist, bounds/transitions, conflict
228
262
  guard, approval, and writeback mode.
229
263
 
264
+ ## Bounded set operation fields
265
+
266
+ A set operation is portable reviewed authority, so it can arrive from a
267
+ contract or the equivalent embedded capability:
268
+
269
+ ```json
270
+ {
271
+ "operation": {
272
+ "kind": "update",
273
+ "cardinality": "set",
274
+ "selection": {
275
+ "all": [{ "column": "status", "operator": "eq", "value": "overdue" }]
276
+ },
277
+ "max_rows": 10,
278
+ "aggregate_bounds": [
279
+ { "column": "balance_cents", "measure": "before", "maximum": 50000 }
280
+ ],
281
+ "version_advance": { "column": "version", "strategy": "integer_increment" }
282
+ },
283
+ "approval": { "mode": "human", "required_role": "billing_reviewer" },
284
+ "writeback": { "mode": "direct_sql" }
285
+ }
286
+ ```
287
+
288
+ Set UPDATE/DELETE requires one through eight fixed equality terms in
289
+ `selection.all`; the model cannot add or override them. Every set requires
290
+ `max_rows` from 1 through 100 and one through eight aggregate bounds. Set
291
+ UPDATE requires integer version advancement. Batch INSERT instead declares
292
+ `operation.batch.items_from_arg` pointing to a bounded `object_array` argument
293
+ and item-field dedup components. All forms require human/operator approval and
294
+ direct SQL writeback in 1.3. See [Bounded Set
295
+ Writeback](bounded-set-writeback.md).
296
+
230
297
  ## Executors
231
298
 
232
299
  ```json
@@ -247,7 +314,10 @@ guard, approval, and writeback mode.
247
314
  }
248
315
  ```
249
316
 
250
- Types are `sql_update`, `http_handler`, and `command_handler`. HTTP handlers use
317
+ Types are `sql_update`, `http_handler`, and `command_handler`. The legacy
318
+ `sql_update` executor identifier now dispatches the reviewed native operation
319
+ (`insert`, `update`, or `delete`) and remains named for config compatibility.
320
+ HTTP handlers use
251
321
  `url_env`, optional `POST|PUT|PATCH`, bearer auth, signing secret, and timeout.
252
322
  Command handlers use `command_env` and timeout. Secrets stay in the environment.
253
323
  See [Writeback Executors](writeback-executors.md).
@@ -382,9 +452,10 @@ capability permissions, and trusted session metadata.
382
452
 
383
453
  ## Direct SQL readiness
384
454
 
385
- An administrator must apply `writeback migration` once. The steady-state writer
386
- needs target-table SELECT/allowed UPDATE plus receipt-table
387
- SELECT/INSERT/UPDATE; it does not need schema CREATE.
455
+ For `source_db` + `precreated`, an administrator applies `writeback migration`
456
+ once. The steady-state writer needs target-table `SELECT` and only the reviewed
457
+ operation DML plus receipt-table `SELECT`/`INSERT`/`UPDATE`; it does not need
458
+ schema `CREATE`.
388
459
 
389
460
  ```bash
390
461
  synapsor-runner writeback migration --engine postgres --schema synapsor
@@ -393,5 +464,8 @@ synapsor-runner writeback grants --engine postgres \
393
464
  synapsor-runner doctor --config ./synapsor.runner.json --check-writeback
394
465
  ```
395
466
 
396
- The doctor probe uses a rolled-back receipt insert and target-table check. It
397
- does not mutate business rows.
467
+ `source_db` + `auto_migrate` runs that fixed migration idempotently and needs
468
+ bounded `CREATE`. `runner_ledger` skips source receipt DDL/DML and validates its
469
+ authoritative ledger topology. Doctor uses rollback-only receipt and target
470
+ checks and does not mutate business rows. See
471
+ [Guarded Single-Row CRUD Writeback](guarded-crud-writeback.md).
@@ -89,7 +89,8 @@ worker termination during an open write transaction rolls back and recovers
89
89
  worker termination after commit recovers as already-applied
90
90
  source-down, read-only-ledger, and timeout readiness failures recover without restart
91
91
  shared dead letters requeue or discard with receipts and events preserved
92
- Postgres and MySQL pool queues fail fast at configured bounds
92
+ Postgres and MySQL pool queues fail fast at configured bounds with retry hints
93
+ shared runtime-store batch apply preserves one authoritative bridge and durable receipts
93
94
  backup digest and clean restore match
94
95
  retention archives before delete and preserves active proposals
95
96
  ```
@@ -20,6 +20,13 @@ trusted context, evidence handles, query audit, local inspection, and
20
20
  proposal-first writes. Proposal workflows add full replay across evidence,
21
21
  approval, writeback receipts, and events.
22
22
 
23
+ Prompt instructions and application validation are separate layers. Prompts
24
+ can guide behavior but cannot supply authorization. A fixed, parameterized
25
+ application tool can be a sound boundary for a small system; Runner adds a
26
+ portable reviewed contract and a consistent proposal, approval, receipt,
27
+ replay, and compensation lifecycle. See [Why Synapsor Over Prompt And
28
+ Application Guardrails](why-synapsor-vs-app-guardrails.md).
29
+
23
30
  The model-facing MCP server exposes reviewed semantic tools such as
24
31
  `billing.inspect_invoice` and `billing.propose_late_fee_waiver`.
25
32
 
@@ -67,17 +74,35 @@ outside the model-facing MCP server and verifies:
67
74
  - allowed mutable columns;
68
75
  - conflict/version guard;
69
76
  - idempotency key;
77
+ - operation-specific version or source-unique deduplication guard;
70
78
  - job expiry;
71
- - exactly one affected row.
79
+ - exactly one reviewed row or every member of one bounded frozen set.
72
80
 
73
81
  If any authority check cannot be verified, the write fails closed.
74
82
 
75
83
  For direct SQL writeback, the writer connection is the env var named by the
76
- source `write_url_env` in `synapsor.runner.json`. Direct SQL writeback writes an
77
- administrator-created `synapsor_writeback_receipts` table for idempotency and
78
- replay. The writer needs `SELECT`/`INSERT`/`UPDATE` on that table, but not schema
79
- `CREATE`. If your database policy forbids the receipt table, use an app-owned
80
- `http_handler` or `command_handler` executor instead.
84
+ source `write_url_env` in `synapsor.runner.json`. With `source_db` receipt
85
+ authority, the source mutation and receipt commit atomically; the receipt table
86
+ can be administrator-precreated or explicitly auto-migrated. With
87
+ `runner_ledger` authority, Runner creates no source receipt table, but a crash
88
+ after source commit and before ledger completion can require verified operator
89
+ reconciliation. It is not distributed exactly-once. See
90
+ [Guarded Single-Row CRUD Writeback](guarded-crud-writeback.md).
91
+
92
+ Bounded-set direct writeback additionally requires a contract-fixed predicate
93
+ or complete reviewed item list, `MAX ROWS` plus aggregate bounds,
94
+ human/operator approval, exact frozen identities and versions, deterministic
95
+ locks, one atomic source transaction, and per-member receipt digests. The
96
+ model cannot supply the predicate or cap. See [Bounded Set
97
+ Writeback](bounded-set-writeback.md).
98
+
99
+ When a direct SQL capability opts into reviewed reversibility, an applied
100
+ receipt may carry a bounded inverse containing only trusted identity, version,
101
+ and reviewed writable values. `revert` is operator-only and creates a new
102
+ proposal; it never mutates or approves. Compensation rechecks the exact forward
103
+ result under the trusted tenant and uses the normal approval, receipt,
104
+ reconciliation, and replay boundaries. See [Reviewed Reversible Change
105
+ Sets](reversible-change-sets.md).
81
106
 
82
107
  When a capability uses an `http_handler` or `command_handler` executor, the
83
108
  same approval boundary applies. The runner sends a structured proposal/job
@@ -96,6 +121,8 @@ approve/reject actions. It does not expose raw SQL, database URLs, write
96
121
  credentials, approval tools, commit tools, or controls that widen reviewed
97
122
  tables/columns.
98
123
 
99
- Synapsor Runner supports reviewed single-row business actions only in the
100
- current alpha. It does not support arbitrary SQL, DDL, `INSERT`, `DELETE`,
101
- `UPSERT`, or multi-row updates.
124
+ Synapsor Runner supports reviewed single-row CRUD and the narrowly bounded set
125
+ path documented above. It does not support arbitrary SQL, DDL, UPSERT,
126
+ model-generated predicates, unbounded sets, or cross-table direct writeback.
127
+ Hard DELETE fails closed when cascades, write triggers, or required metadata
128
+ visibility prevent a bounded-effect proof.