@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
@@ -10,7 +10,110 @@ npx -y -p @synapsor/runner synapsor-runner demo --quick
10
10
  The OSS runner command is `synapsor-runner`. The `synapsor` command is reserved
11
11
  for the Synapsor Cloud CLI.
12
12
 
13
- ## 1.1.0 (prepared, not published)
13
+ ## 1.4.1 (prepared, not published)
14
+
15
+ ### Bounded-set digest compatibility patch
16
+
17
+ - Contract-authored bounded-set proposals now use deterministic recursive
18
+ object-key ordering for member and set digest material.
19
+ - Valid proposals created by `1.4.0` remain applyable; the compatibility path
20
+ accepts only the known deterministic `1.4.0` serializations reconstructed
21
+ from the complete stored reviewed data.
22
+ - Genuine member, version, value, aggregate, membership, or tenant drift still
23
+ fails closed before source mutation on PostgreSQL and MySQL.
24
+ - The Runner package now includes the linked bounded-set guide and validates
25
+ all shipped local Markdown links while packaging.
26
+ - The DSL package description and README no longer label the current `1.4.x`
27
+ package as a `0.1 preview`. Canonical contract `spec_version: "0.1"` is
28
+ unchanged.
29
+ - Adds an honest prompt-and-application-guardrails decision guide covering SQL
30
+ authority, hand-built semantic tools, structural enforcement, build-vs-adopt
31
+ fit, and regulated-data boundaries.
32
+
33
+ Prepared package versions: `@synapsor/dsl@1.4.1` and
34
+ `@synapsor/runner@1.4.1`. `@synapsor/spec` remains `1.4.0`.
35
+
36
+ ## 1.4.0 (prepared, not published)
37
+
38
+ ### Reviewed Reversible Change Sets
39
+
40
+ - Adds canonical and DSL opt-in reversibility for direct SQL operations with
41
+ human/operator approval and exact version/dedup guards.
42
+ - Records only bounded, allowlisted inverse data after an unambiguous apply.
43
+ - Adds operator-only `revert`, which creates a new independently reviewed
44
+ proposal and never writes, approves, or becomes model-facing.
45
+ - Proves UPDATE, INSERT, soft-delete, and exact bounded-set compensation on
46
+ PostgreSQL and MySQL, including stale-state refusal, atomic set rollback,
47
+ crash reconciliation, inverse redaction, receipts, and replay.
48
+ - Keeps hard DELETE restoration, app-owned handlers, payments, messages, and
49
+ other external effects outside Runner's automatic compensation claim.
50
+
51
+ Prepared package versions: `@synapsor/spec@1.4.0`,
52
+ `@synapsor/dsl@1.4.0`, and `@synapsor/runner@1.4.0`.
53
+
54
+ ## 1.3.0 (prepared, not published)
55
+
56
+ ### Bounded Set Writeback
57
+
58
+ - Adds fixed-predicate set UPDATE/DELETE and exact-review batch INSERT for
59
+ PostgreSQL and MySQL, capped at 100 rows.
60
+ - Requires mandatory row and aggregate-value bounds, a frozen exact target set,
61
+ integer version guards for set UPDATE, human/operator approval, atomic apply,
62
+ and per-member receipt/replay evidence.
63
+ - Rejects cap overflow instead of truncating, rolls back the full transaction
64
+ on any stale or failing member, and refuses hard set DELETE when hidden
65
+ triggers or widening cascades are present.
66
+ - Keeps model-generated predicates, policy auto-approval for sets, unbounded
67
+ batches, UPSERT, cross-table transactions, and external effects on the
68
+ app-owned executor path.
69
+ - Adds `corepack pnpm test:bounded-set`, which runs the safety matrix and local
70
+ 1/10/100-row measurements against disposable PostgreSQL and MySQL.
71
+
72
+ Prepared package versions: `@synapsor/spec@1.3.0`,
73
+ `@synapsor/dsl@1.3.0`, and `@synapsor/runner@1.3.0`.
74
+
75
+ ## 1.2.0 (prepared, not published)
76
+
77
+ ### Guarded CRUD And Receipt Authority
78
+
79
+ - Adds canonical and DSL operation semantics for guarded single-row INSERT,
80
+ UPDATE, and DELETE while preserving legacy UPDATE contracts.
81
+ - Adds atomic source receipts with precreated or auto-migrated tables and an
82
+ opt-in Runner-ledger mode that creates no source receipt table.
83
+ - Adds durable writeback intents and a fail-closed operator reconciliation
84
+ workflow for ambiguous ledger/source crash windows.
85
+ - Extends onboarding, doctor, preview, schema inspection, protocol v2,
86
+ Postgres/MySQL adapters, the support-plan-credit example, and disposable live
87
+ conformance tests.
88
+ - Keeps multi-row writes, UPSERT, DDL, cross-table work, and external effects
89
+ on the app-owned executor path.
90
+
91
+ Prepared package versions: `@synapsor/spec@1.2.0`,
92
+ `@synapsor/dsl@1.2.0`, and `@synapsor/runner@1.2.0`.
93
+
94
+ ## 1.1.2 (prepared, not published)
95
+
96
+ ### Retry And Shared Batch Correctness
97
+
98
+ - Makes transient source pool and recognized database saturation errors
99
+ consistently retryable with a bounded retry hint and safe normalized logs.
100
+ - Fixes `apply --all-approved` in shared `runtime_store` mode so every selected
101
+ proposal uses the same authoritative bridge and durable outcome ledger.
102
+
103
+ Prepared package version: `@synapsor/runner@1.1.2`. Canonical Spec and DSL stay
104
+ at `1.1.0`.
105
+
106
+ ## 1.1.1
107
+
108
+ ### Resource Read Authorization
109
+
110
+ - Reauthorizes local proposal, evidence, and replay resources against the
111
+ current trusted tenant and principal instead of treating opaque handles as
112
+ bearer authority.
113
+
114
+ Released package version: `@synapsor/runner@1.1.1`.
115
+
116
+ ## 1.1.0
14
117
 
15
118
  ### Bounded Small-Fleet Runtime
16
119
 
@@ -27,7 +130,7 @@ for the Synapsor Cloud CLI.
27
130
  - Adds [Running A Small Runner Fleet](running-a-runner-fleet.md) and the
28
131
  `corepack pnpm test:fleet` synthetic two-Runner verification.
29
132
 
30
- Prepared package versions: `@synapsor/spec@1.1.0`,
133
+ Released package versions: `@synapsor/spec@1.1.0`,
31
134
  `@synapsor/dsl@1.1.0`, and `@synapsor/runner@1.1.0`.
32
135
 
33
136
  ## 1.0.0
@@ -123,7 +123,7 @@ through the `1.x` line unless a release note marks a deprecation first:
123
123
 
124
124
  Stable does not promise production SLA, hosted Cloud features, compliance
125
125
  certification, physical Postgres/MySQL branching, generic SQL writeback,
126
- generic multi-row writes, or compatibility for undocumented local SQLite
126
+ free-form/unbounded set writes, or compatibility for undocumented local SQLite
127
127
  internals. Local store migrations may happen inside `1.x` and later minor
128
128
  versions, but documented CLI inspection commands should remain the supported way
129
129
  to read the store.
@@ -86,6 +86,7 @@ database.
86
86
  "approval_required": true,
87
87
  "writeback": {
88
88
  "mode": "direct_update",
89
+ "operation": "update",
89
90
  "applied": false
90
91
  },
91
92
  "next": "A human must approve outside this model-facing tool surface; nothing is committed yet."
@@ -101,12 +102,22 @@ database.
101
102
  }
102
103
  ```
103
104
 
104
- For app-owned executors, `proposal.writeback.mode` is `app_handler`.
105
+ For app-owned executors, `proposal.writeback.mode` is `app_handler`. Native
106
+ guarded proposals use `direct_insert`, `direct_update`, or `direct_delete`, and
107
+ `proposal.writeback.operation` is `insert`, `update`, or `delete`. Every
108
+ proposal result still reports `source_database_changed: false` until external
109
+ apply.
110
+
111
+ For bounded sets, the same model-facing v2 envelope reports the operation,
112
+ reviewed member count, row cap, aggregate values/maximums, set digest, and exact
113
+ allowlisted member diffs. Protocol v3 is used behind the envelope for the
114
+ frozen job and receipt; see [Result Envelope v3](result-envelope-v3.md).
105
115
 
106
116
  ## Error Result
107
117
 
108
- Model-facing errors are safe and stable. Raw driver details stay in local logs
109
- or ledger inspection, not in the MCP result.
118
+ Model-facing errors are safe and stable. Structured tool-rejection logs also
119
+ omit raw driver messages, hosts, and credentials; use operator-controlled
120
+ readiness and database diagnostics to investigate the underlying dependency.
110
121
 
111
122
  ```json
112
123
  {
@@ -119,7 +130,8 @@ or ledger inspection, not in the MCP result.
119
130
  "error": {
120
131
  "code": "TEMPORARILY_UNAVAILABLE",
121
132
  "message": "The database is temporarily unavailable. Retry later.",
122
- "retryable": true
133
+ "retryable": true,
134
+ "retry_after_ms": 1000
123
135
  },
124
136
  "evidence": null,
125
137
  "source_database_changed": false,
@@ -143,8 +155,10 @@ TEMPORARILY_UNAVAILABLE
143
155
  INTERNAL
144
156
  ```
145
157
 
146
- Current alpha implementation redacts raw connection and driver messages from v2
147
- MCP results. New `init` and `onboard db` configs write `result_format: 2` by
158
+ Runner redacts raw connection and driver messages from v2 MCP results and
159
+ structured operational logs. Recognized transient pool, PostgreSQL, MySQL, and
160
+ network failures include a bounded `retry_after_ms`; unrelated database errors
161
+ remain non-retryable. New `init` and `onboard db` configs write `result_format: 2` by
148
162
  default. Existing hand-written configs without `result_format` keep the legacy
149
163
  runtime default for compatibility; pass `--result-format v2` when serving an
150
164
  older config to force the v2 envelope, or `--result-format v1` for an older
@@ -0,0 +1,43 @@
1
+ # Result Envelope V3
2
+
3
+ Protocol v3 is used only for bounded set direct writeback. Protocol v1 and v2
4
+ remain accepted for legacy UPDATE and guarded single-row CRUD.
5
+
6
+ The public artifacts are:
7
+
8
+ - `synapsor.change-set.v3`;
9
+ - `synapsor.writeback-job.v3`;
10
+ - `synapsor.execution-receipt.v3`.
11
+
12
+ Each v3 object names one operation: `set_update`, `set_delete`, or
13
+ `batch_insert`. It carries the same frozen-set authority:
14
+
15
+ ```json
16
+ {
17
+ "max_rows": 10,
18
+ "row_count": 2,
19
+ "aggregate_bounds": [
20
+ { "column": "balance_cents", "measure": "before", "maximum": 20000, "actual": 15000 }
21
+ ],
22
+ "members": [
23
+ {
24
+ "primary_key": { "column": "id", "value": "INV-1" },
25
+ "expected_version": { "column": "version", "value": 1 },
26
+ "before_digest": "sha256:...",
27
+ "after_digest": "sha256:..."
28
+ }
29
+ ],
30
+ "set_digest": "sha256:..."
31
+ }
32
+ ```
33
+
34
+ The complete protocol fixtures live in [`fixtures/protocol`](../fixtures/protocol)
35
+ and the JSON Schemas live in [`schemas`](../schemas). The execution receipt
36
+ reports every `target_identity` and matching `member_effect`, plus receipt
37
+ authority, set digest, exact affected count, safe status/error, and receipt
38
+ hash. An applied result is invalid unless its affected count and member-effect
39
+ count match the frozen identity count.
40
+
41
+ `runner_ledger` may return `reconciliation_required` with an intent ID when a
42
+ cross-database crash window prevents proof of commit. That is a terminal
43
+ operator state, not a retry instruction or an `already_applied` guess.
@@ -0,0 +1,47 @@
1
+ # Compensation Protocol v4
2
+
3
+ Protocol v4 is used only for reviewed compensation. Existing v1 single-row,
4
+ v2 CRUD, and v3 bounded-set objects keep their meaning.
5
+
6
+ Public objects:
7
+
8
+ - `synapsor.compensation-change-set.v1`;
9
+ - `synapsor.inverse-descriptor.v1`;
10
+ - `synapsor.writeback-job.v4`;
11
+ - `synapsor.execution-receipt.v4`.
12
+
13
+ The compensation change set is a normal proposal with a new proposal ID,
14
+ principal, tenant scope, evidence, approval requirement, integrity hash, and a
15
+ link to the exact applied forward receipt. It does not authorize execution.
16
+
17
+ The inverse descriptor records:
18
+
19
+ - `availability` plus specific `reason_codes` when unavailable;
20
+ - `restore_update`, `remove_insert`, or `restore_insert`;
21
+ - single-row or exact-set cardinality;
22
+ - fixed source, table, tenant guard, and allowed columns;
23
+ - exact primary-key members, expected forward state, and restore values;
24
+ - original row/aggregate caps and integer version advancement;
25
+ - root/parent/reverted proposal lineage and bounded depth.
26
+
27
+ A v4 writeback job is created only after the compensation proposal is
28
+ independently approved. The adapter locks and validates every member before
29
+ mutation, applies in one source transaction, and emits a v4 receipt. A
30
+ successful receipt contains the compensation's own bounded inverse. A stale or
31
+ missing member returns conflict without a partial effect.
32
+
33
+ With Runner-ledger receipt authority, an unacknowledged commit or rollback
34
+ enters `reconciliation_required`; no inverse is synthesized until a verified
35
+ operator resolves the outcome.
36
+
37
+ Canonical JSON Schemas are under:
38
+
39
+ ```text
40
+ schemas/inverse-descriptor.v1.schema.json
41
+ schemas/compensation-change-set.v1.schema.json
42
+ schemas/writeback-job.v4.schema.json
43
+ schemas/execution-receipt.v4.schema.json
44
+ ```
45
+
46
+ See [Reviewed Reversible Change Sets](reversible-change-sets.md) for the
47
+ operator flow and supported operation matrix.
@@ -0,0 +1,151 @@
1
+ # Reviewed Reversible Change Sets
2
+
3
+ Synapsor Runner 1.4 can capture a bounded inverse for an opt-in direct database
4
+ write. This is **reviewed compensation**, not rollback or database time travel.
5
+
6
+ The operator flow is deliberately two separate approval loops:
7
+
8
+ ```text
9
+ forward proposal -> approval -> guarded apply -> applied receipt with inverse
10
+ |
11
+ v
12
+ operator runs revert -> new proposal -> independent approval -> guarded apply
13
+ ```
14
+
15
+ `synapsor-runner revert` never writes, approves, or bypasses a guard. It is an
16
+ operator CLI command and is not exposed as an MCP tool.
17
+
18
+ ## Authoring
19
+
20
+ Add `REVERSIBLE` only to a direct SQL proposal whose inverse Runner can prove:
21
+
22
+ ```sql
23
+ CREATE CAPABILITY billing.propose_plan_credit
24
+ USING CONTEXT trusted_operator
25
+ SOURCE billing_postgres
26
+ ON public.customers
27
+ PRIMARY KEY id
28
+ TENANT KEY tenant_id
29
+ CONFLICT GUARD version
30
+ LOOKUP customer_id BY id
31
+ ARG customer_id STRING REQUIRED MAX LENGTH 128
32
+ ARG amount_cents NUMBER REQUIRED MIN 1 MAX 2500
33
+ ALLOW READ id, tenant_id, plan_credit_cents, version
34
+ REQUIRE EVIDENCE
35
+ MAX ROWS 1
36
+ PROPOSE ACTION grant_plan_credit UPDATE
37
+ ALLOW WRITE plan_credit_cents
38
+ PATCH plan_credit_cents = ARG amount_cents
39
+ BOUND plan_credit_cents 0..2500
40
+ ADVANCE VERSION version USING INTEGER INCREMENT
41
+ APPROVAL ROLE support_reviewer
42
+ WRITEBACK DIRECT SQL
43
+ REVERSIBLE
44
+ END
45
+ ```
46
+
47
+ The canonical JSON equivalent is:
48
+
49
+ ```json
50
+ {
51
+ "proposal": {
52
+ "reversibility": { "mode": "reviewed_inverse" }
53
+ }
54
+ }
55
+ ```
56
+
57
+ Validation rejects `REVERSIBLE` when writeback uses an app handler or Cloud
58
+ worker, approval is policy-driven, or an UPDATE lacks exact integer version
59
+ advancement. INSERT also requires a deterministic primary-key dedup component.
60
+
61
+ ## Operator Flow
62
+
63
+ After an unambiguous forward apply:
64
+
65
+ ```bash
66
+ synapsor-runner revert <forward_proposal_id> \
67
+ --config ./synapsor.runner.json \
68
+ --store ./.synapsor/local.db \
69
+ --reason "Correct the reviewed credit"
70
+
71
+ synapsor-runner proposals show latest --details --store ./.synapsor/local.db
72
+ synapsor-runner proposals approve latest --yes --store ./.synapsor/local.db
73
+ synapsor-runner apply latest --config ./synapsor.runner.json --store ./.synapsor/local.db
74
+ synapsor-runner replay show latest --store ./.synapsor/local.db
75
+ ```
76
+
77
+ The compensation proposal inherits the original reviewer role and quorum. A
78
+ signed operator identity is required when configured. The source remains
79
+ unchanged until the compensation proposal is separately approved and applied.
80
+
81
+ ## Supported Inverses
82
+
83
+ | Forward operation | Reviewed compensation |
84
+ | --- | --- |
85
+ | Single-row UPDATE | Restores only allowlisted prior values after checking the exact state and version left by the forward write. The version advances again; it is never decremented. |
86
+ | Soft delete | Same as UPDATE when deletion is a reviewed status or timestamp patch. |
87
+ | Single-row INSERT | Deletes only the exact Runner-created identity after checking current reviewed state, tenant, triggers, and foreign-key effects. |
88
+ | Bounded set UPDATE | Restores exactly the original frozen members under the original count/value caps in one transaction. One stale member aborts all members. |
89
+ | Exact-review batch INSERT | Removes exactly the inserted member identities, atomically, after the same safety preflight. |
90
+ | Hard DELETE | Not generally restorable. The receipt reports specific unavailable reason codes instead of claiming a safe inverse. |
91
+
92
+ Kept-out and non-writable fields are never captured just to make compensation
93
+ possible. Each successful compensation captures its own bounded inverse, so an
94
+ operator may propose a revert of the compensation. Lineage is linear, duplicate
95
+ children are rejected, and traversal is capped at 16 proposals.
96
+
97
+ ## Fail-Closed Cases
98
+
99
+ Runner refuses to create or apply compensation when:
100
+
101
+ - the forward outcome is missing, failed, or requires reconciliation;
102
+ - the applied receipt has no available inverse;
103
+ - another active compensation already targets that receipt;
104
+ - the tenant or operator identity does not match trusted authority;
105
+ - the row changed after the forward write;
106
+ - an inserted row is missing or has changed;
107
+ - a new trigger, foreign-key dependent, or widening cascade prevents a bounded
108
+ effect proof;
109
+ - any member of a bounded set is stale or missing;
110
+ - lineage is invalid, cyclic, or beyond the depth limit.
111
+
112
+ With `runner_ledger` receipt authority, a crash after source commit but before
113
+ ledger completion remains ambiguous. Forward and compensation writes enter
114
+ `reconciliation_required`; Runner does not call them reverted or retry them
115
+ automatically. Reconcile the outcome first through the verified operator
116
+ workflow.
117
+
118
+ ## Database Metadata Privileges
119
+
120
+ Postgres uses catalog visibility available to the configured writer for
121
+ trigger and foreign-key preflight.
122
+
123
+ MySQL compensation that may delete a row, including reversal of INSERT,
124
+ requires enough metadata visibility to prove no hidden widening effects:
125
+
126
+ - scoped `TRIGGER` visibility on the target table;
127
+ - global `PROCESS` visibility for incoming InnoDB foreign-key metadata.
128
+
129
+ If those privileges are not acceptable, use a reversible soft-delete UPDATE or
130
+ an explicitly reviewed app-owned compensation capability. Runner fails closed.
131
+
132
+ ## App-Owned Executors
133
+
134
+ Runner does not infer compensation for `http_handler`, `command_handler`,
135
+ payments, messages, or other external effects. The application must expose a
136
+ separate reviewed compensation capability and enforce its own tenant, version,
137
+ idempotency, and transaction rules. A handler returning a receipt does not make
138
+ its side effect Runner-reversible.
139
+
140
+ ## Verification
141
+
142
+ The disposable PostgreSQL/MySQL gate proves UPDATE, INSERT, soft-delete, exact
143
+ bounded-set compensation, independent approval, stale-state refusal,
144
+ hard-delete unavailability, inverse redaction, receipts, and replay:
145
+
146
+ ```bash
147
+ corepack pnpm test:reversible
148
+ ```
149
+
150
+ See [RFC 006](rfcs/006-reviewed-reversible-change-sets.md) for the normative
151
+ safety design and [Current Limitations](limitations.md) for the wider boundary.
@@ -0,0 +1,165 @@
1
+ # RFC 004: Guarded CRUD Receipt Authority
2
+
3
+ Status: implementation gate
4
+
5
+ ## Purpose
6
+
7
+ Runner 1.2 adds guarded single-row INSERT and DELETE while making source receipt
8
+ DDL optional. It must preserve the defining boundary: a reviewed, tenant-bound,
9
+ one-row business effect with evidence, external approval, a receipt, and replay.
10
+
11
+ This RFC is required before adapter expansion because receipt placement changes
12
+ which outcomes can be proven after a crash.
13
+
14
+ ## Configuration Model
15
+
16
+ Two decisions are orthogonal:
17
+
18
+ ```text
19
+ receipt_authority: source_db | runner_ledger
20
+ source_receipt_provisioning: precreated | auto_migrate
21
+ ```
22
+
23
+ `source_receipt_provisioning` is valid only for `source_db`.
24
+
25
+ For compatibility, omitted fields mean `source_db` plus `precreated`. A new
26
+ wizard may recommend `auto_migrate`, but it must obtain explicit consent before
27
+ granting Runner DDL authority. `runner_ledger` is opt-in and must explain its
28
+ post-commit ambiguity.
29
+
30
+ Receipt schema/table identifiers are operator config, validated as fixed safe
31
+ identifiers. They are never contract fields or model arguments.
32
+
33
+ ## Terms
34
+
35
+ - **Exactly-once classification:** Runner can prove whether this proposal was
36
+ applied, conflicted, or failed.
37
+ - **At-most-once business effect:** the source cannot perform the reviewed
38
+ effect twice for the same deterministic identity.
39
+ - **Idempotent effect:** repeating an operation converges on the same source
40
+ state, but may not prove who caused it.
41
+ - **Ambiguous outcome:** the source may have committed, but Runner lacks an
42
+ atomic receipt proving that fact. This requires reconciliation.
43
+
44
+ These terms are not interchangeable.
45
+
46
+ ## Claim Matrix
47
+
48
+ The claims below apply equally to Postgres and MySQL. Engine-specific tests must
49
+ prove them separately.
50
+
51
+ | Operation | Authority | Source prerequisite | Normal result | Retry after completed receipt | Crash after source commit, before Runner completion |
52
+ | --- | --- | --- | --- | --- | --- |
53
+ | UPDATE | `source_db` | exact PK+tenant+version guard; source receipt table | atomic one-row UPDATE and receipt | `already_applied` | atomic transaction proves applied or rolled back |
54
+ | INSERT | `source_db` | trusted tenant; reviewed insert columns; proposal-specific PK/UNIQUE dedup key; source receipt table | atomic one-row INSERT and receipt | `already_applied` | atomic transaction proves applied or rolled back |
55
+ | DELETE | `source_db` | exact PK+tenant+version guard; no expanding cascade/write trigger; source receipt table | atomic one-row DELETE and receipt | `already_applied` | atomic transaction proves applied or rolled back |
56
+ | UPDATE | `runner_ledger` | authoritative shared ledger for fleets; exact PK+tenant+version guard; monotonic version advancement in source transaction | intent, one-row UPDATE, terminal ledger receipt | completed matching receipt returns `already_applied` | advanced guard plus incomplete intent is `reconciliation_required` |
57
+ | INSERT | `runner_ledger` | authoritative shared ledger for fleets; proposal-specific source PK/UNIQUE key | intent, one-row INSERT, terminal ledger receipt | completed matching receipt returns `already_applied` | matching unique row plus incomplete intent is reconciled only when provenance is provable; otherwise `reconciliation_required` |
58
+ | DELETE | `runner_ledger` | authoritative shared ledger for fleets; exact PK+tenant+version guard; no expanding side effects | intent, one-row DELETE, terminal ledger receipt | completed matching receipt returns `already_applied` | missing row plus incomplete intent is `reconciliation_required`, not proof of this delete |
59
+
60
+ ## Failure and Crash Matrix
61
+
62
+ | Point/outcome | `source_db` | `runner_ledger` |
63
+ | --- | --- | --- |
64
+ | ledger/source unavailable before intent | no source effect; retryable unavailable | no source effect; retryable unavailable |
65
+ | crash before source BEGIN | no source effect | durable intent remains; safe retry only if no ambiguous attempt was entered |
66
+ | crash after BEGIN, before mutation | transaction rolls back | source transaction rolls back; intent can be marked failed/retried by bounded operator/worker logic |
67
+ | stale/version conflict | conflict receipt commits atomically | conflict terminal event is recorded; no source mutation |
68
+ | crash after mutation, before source COMMIT | transaction rolls back | source transaction rolls back; no business effect |
69
+ | crash after source COMMIT, before Runner completion | impossible as a split outcome because source receipt shares the transaction | terminal state is `reconciliation_required`; never auto-retry |
70
+ | concurrent identical apply | source receipt serializes one outcome | ledger intent plus source guard/unique key permits at most one effect; ambiguous loser reconciles |
71
+ | source unavailable after intent | no source effect | intent remains failed/retryable only while no source transaction could have committed |
72
+
73
+ ## Operation Proof Obligations
74
+
75
+ ### UPDATE
76
+
77
+ Runner-ledger UPDATE requires a real version guard and monotonic advancement in
78
+ the same source transaction. Accepted strategies are a Runner-generated integer
79
+ increment or a database-generated version whose changed value is verified
80
+ before commit. A name such as `updated_at` is not proof. Insufficient timestamp
81
+ precision fails preflight.
82
+
83
+ An advanced source version plus an incomplete intent is ambiguous, not
84
+ `already_applied`.
85
+
86
+ ### INSERT
87
+
88
+ INSERT requires a proposal-specific deterministic identity backed by an
89
+ inspected PRIMARY KEY or UNIQUE constraint. The model cannot set tenant,
90
+ identity/generated columns, or arbitrary columns. The contract declares the
91
+ portable dedup mapping; Runner config declares receipt placement.
92
+
93
+ Ledger memory by itself does not prevent a duplicate INSERT after a process
94
+ crash. A source uniqueness constraint is mandatory.
95
+
96
+ ### DELETE
97
+
98
+ DELETE targets one reviewed row by primary key, trusted tenant, and expected
99
+ version. The proposal freezes allowlisted before evidence. A hard delete is
100
+ refused when cascading foreign keys or write triggers can expand the reviewed
101
+ effect. Soft delete remains the preferred path.
102
+
103
+ A missing row after an incomplete Runner-ledger attempt proves only that the
104
+ row is absent, not that this attempt deleted it.
105
+
106
+ ## Durable Runner-Ledger Intent
107
+
108
+ Before touching the source, Runner durably stores:
109
+
110
+ - proposal ID, version, and hash;
111
+ - capability and operation;
112
+ - trusted tenant and target/dedup identity;
113
+ - expected guard;
114
+ - bounded attempt number;
115
+ - state and timestamps.
116
+
117
+ Allowed states are:
118
+
119
+ ```text
120
+ intent_recorded
121
+ applying
122
+ applied
123
+ already_applied
124
+ conflict
125
+ failed
126
+ reconciliation_required
127
+ ```
128
+
129
+ Transitions append immutable events. An incomplete `applying` attempt is never
130
+ automatically restarted if the source may have committed.
131
+
132
+ ## Reconciliation
133
+
134
+ The operator command re-reads only the reviewed target under trusted tenant
135
+ scope and displays expected versus observed allowlisted metadata. It never
136
+ guesses. A verified operator can mark `applied`, `conflict`, or `failed` with a
137
+ reason; Runner appends a reconciliation event and receipt. Production signed
138
+ identity requirements apply unchanged.
139
+
140
+ ## Topology Rules
141
+
142
+ - local SQLite Runner-ledger authority is allowed only for one local operator
143
+ process;
144
+ - Streamable HTTP fleets and multiple workers require shared Postgres
145
+ `runtime_store` as the authoritative ledger;
146
+ - `mirror` is not authoritative enough for Runner-ledger write intents;
147
+ - unsafe topology fails at config/doctor/start preflight.
148
+
149
+ ## Security Invariants
150
+
151
+ - raw SQL, receipt controls, identifiers, approval, apply, credentials, tenant,
152
+ and principal remain outside model-facing tools;
153
+ - all identifiers are contract/config validated and all values are parameters;
154
+ - unknown, stale, ambiguous, or multi-row effects fail closed;
155
+ - kept-out fields are not persisted in intents, receipts, metrics, or replay;
156
+ - local resource reads continue to reauthorize tenant and principal;
157
+ - no claim of distributed atomic exactly-once is made for `runner_ledger`.
158
+
159
+ ## Bounded Set Gate
160
+
161
+ This RFC does not authorize multi-row writes. Set operations require a separate
162
+ RFC with fixed typed predicates, frozen exact targets, mandatory count/value
163
+ bounds, deterministic row locking, one transaction, exact receipts, and all
164
+ R1-R7 threat mitigations. `MAX ROWS` syntax alone is not shippable.
165
+