@synapsor/runner 0.1.15 → 1.0.0

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.
@@ -1,7 +1,7 @@
1
1
  # Release Notes
2
2
 
3
- These notes track public Synapsor Runner behavior. Starting with `0.1.0`, the
4
- normal install path uses the untagged stable package:
3
+ These notes track public Synapsor Runner behavior. Starting with `1.0.0`, the
4
+ documented production-loop compatibility line uses the untagged stable package:
5
5
 
6
6
  ```bash
7
7
  npx -y -p @synapsor/runner synapsor-runner demo --quick
@@ -12,6 +12,48 @@ for the Synapsor Cloud CLI.
12
12
 
13
13
  ## Unreleased
14
14
 
15
+ ## 1.0.0
16
+
17
+ ### Production Approval Loop
18
+
19
+ - Adds batch apply for approved proposals with independent per-proposal
20
+ outcomes, rerun-safe idempotency, and `--capability`, `--tenant`, and
21
+ `--max` filters.
22
+ - Adds aggregate policy ceilings to canonical contracts and DSL authoring so
23
+ small proposals fall back to human review once daily count or total limits are
24
+ reached.
25
+ - Adds signed operator-key approval/apply enforcement, tamper-evident approval
26
+ records, operational counters/logs, and a supervised local writeback worker.
27
+ - Adds shared Postgres ledger support, runtime-store mode, per-session trusted
28
+ context, managed secret hydration, token rotation hooks, and Streamable HTTP
29
+ mTLS for scale-out deployments.
30
+ - Keeps local SQLite as the default while allowing runtime-store workers to run
31
+ long-lived shared ledger drain loops under a Postgres advisory lock.
32
+ - Declares the first semver contract for the documented CLI, schema, contract,
33
+ MCP result, writeback, approval, metrics, and replay surfaces.
34
+
35
+ Prepared package versions: `@synapsor/spec@1.0.0`,
36
+ `@synapsor/dsl@1.0.0`, and `@synapsor/runner@1.0.0`.
37
+
38
+ ## 0.1.16
39
+
40
+ ### Fleet-Lab Runner Hardening
41
+
42
+ - Preserves Postgres microseconds in proposal conflict guards and proves normal
43
+ `now()` rows apply exactly once while genuinely stale rows conflict.
44
+ - Allows a new proposal after conflict without deleting or rewriting the old
45
+ proposal, receipt, or replay history.
46
+ - Returns semantic active-proposal errors and rejects non-primary DSL lookups.
47
+ - Removes schema-creation requirements from steady-state writeback, aligns audit
48
+ paths and JSON Schema with contract configs, and creates local ledgers with
49
+ owner-only POSIX permissions.
50
+ - Adds complete DSL, Runner config, and ledger inspection/security references.
51
+
52
+ Prepared package versions: `@synapsor/dsl@0.1.6` and
53
+ `@synapsor/runner@0.1.16`. `@synapsor/spec@0.1.4` remains unchanged.
54
+
55
+ ## 0.1.15
56
+
15
57
  ### Editor-Friendly DSL Source Files
16
58
 
17
59
  - Prefers `.synapsor.sql` for DSL source files so editors can provide generic
@@ -24,6 +66,8 @@ Prepared package versions: `@synapsor/dsl@0.1.5` and
24
66
  `@synapsor/runner@0.1.15`. The already-published `@synapsor/spec@0.1.4` does
25
67
  not change and must not be republished for this release.
26
68
 
69
+ ## 0.1.12
70
+
27
71
  ### Runner Version Invocation
28
72
 
29
73
  - Keeps `synapsor-runner --version`, `synapsor-runner -v`, and
@@ -398,9 +442,9 @@ not change and must not be republished for this release.
398
442
  `SYNAPSOR_DATABASE_WRITE_URL`.
399
443
  - `SYNAPSOR_DATABASE_URL` is accepted only as a legacy fallback for older
400
444
  direct worker/apply flows without a local config.
401
- - Direct SQL writeback creates or writes `synapsor_writeback_receipts` for
402
- idempotency and replay. The trusted writer needs permission for that receipt
403
- table, or an administrator must pre-create the table and grant access.
445
+ - Direct SQL writeback writes `synapsor_writeback_receipts` for idempotency and
446
+ replay. Current releases require an administrator-created table and grant the
447
+ trusted writer table access without schema `CREATE`.
404
448
  - Use `synapsor-runner writeback doctor`, `writeback migration`, and
405
449
  `writeback grants` to inspect and prepare the direct writeback path.
406
450
  - Use app-owned `http_handler` or `command_handler` executors for rich writes
@@ -457,10 +501,9 @@ not change and must not be republished for this release.
457
501
 
458
502
  ## Stable Release Policy
459
503
 
460
- Use untagged `@synapsor/runner` for stable installs. Use `@alpha` or an exact
461
- prerelease only when intentionally testing preview behavior. Stable `0.1.x`
462
- releases should keep the compatibility promise documented in
463
- `docs/release-policy.md`.
504
+ Use untagged `@synapsor/runner` for stable installs. Use an exact prerelease
505
+ only when intentionally testing preview behavior. Stable `1.x` releases should
506
+ keep the compatibility promise documented in `docs/release-policy.md`.
464
507
 
465
508
  The first stable `0.1.0` release was gated on:
466
509
 
@@ -1,7 +1,10 @@
1
1
  # Release Policy
2
2
 
3
- Synapsor Runner `0.1.0` is the first stable local runner line. Use the stable
4
- package for normal installs:
3
+ Synapsor Runner `0.1.0` was the first stable local runner line. Synapsor
4
+ Runner `1.0.0` is the first production approval-loop semver line: batch apply,
5
+ aggregate policy limits, verified operator identity, structured operations, and
6
+ shared runtime-store deployment are part of the documented compatibility
7
+ surface. Use the stable package for normal installs:
5
8
 
6
9
  ```bash
7
10
  npx -y -p @synapsor/runner synapsor-runner demo --quick
@@ -55,10 +58,37 @@ A stable `0.1.0` release should only be tagged after:
55
58
  - there are no known docs/code mismatches around transport, credentials,
56
59
  receipt tables, or handler expectations.
57
60
 
61
+ ## 1.0 Stability Gate
62
+
63
+ Do not tag `1.0.0` only because the package is useful. `1.0.0` is the public
64
+ semver contract for the Runner production approval loop, and it should be cut
65
+ only after the following are true in the current tree and release artifacts:
66
+
67
+ - batch apply can apply all approved proposals independently, reports
68
+ applied/conflict/skipped IDs, is safe to rerun through idempotency receipts,
69
+ and supports `--capability`, `--tenant`, and `--max`;
70
+ - aggregate auto-approval limits are authored in DSL, represented in the
71
+ canonical contract spec, enforced as human-review fallback, persisted with
72
+ tripped-limit details, and visible in doctor/tools preview output;
73
+ - approve/reject/apply can require a verified operator identity, enforce
74
+ contract reviewer roles and apply roles, and bind tamper-evident identity
75
+ records into the proposal ledger;
76
+ - designed rejections and writeback outcomes emit structured logs, operational
77
+ counters are available per tenant and capability, and owner-only local store
78
+ permissions remain under test;
79
+ - the documented public surfaces below have release-gate coverage from packed
80
+ artifacts, not only source-tree tests.
81
+
82
+ After `1.0.0`, changes to the documented CLI, schema, contract, MCP result,
83
+ writeback, approval, metrics, and replay surfaces must follow semantic
84
+ versioning. Breaking changes require a new major version, except for security
85
+ fixes that close a vulnerability while preserving the safest possible
86
+ compatibility path.
87
+
58
88
  ## Stable Compatibility Promise
59
89
 
60
- Starting with `0.1.0`, Synapsor Runner keeps these public surfaces compatible
61
- through the `0.1.x` line unless a release note marks a deprecation first:
90
+ Starting with `1.0.0`, Synapsor Runner keeps these public surfaces compatible
91
+ through the `1.x` line unless a release note marks a deprecation first:
62
92
 
63
93
  - the `synapsor-runner` binary name and README quickstart commands;
64
94
  - `synapsor.runner.json` schema version `1` for documented fields;
@@ -73,8 +103,9 @@ through the `0.1.x` line unless a release note marks a deprecation first:
73
103
  Stable does not promise production SLA, hosted Cloud features, compliance
74
104
  certification, physical Postgres/MySQL branching, generic SQL writeback,
75
105
  generic multi-row writes, or compatibility for undocumented local SQLite
76
- internals. Local store migrations may happen inside `0.1.x`, but documented CLI
77
- inspection commands should remain the supported way to read the store.
106
+ internals. Local store migrations may happen inside `1.x` and later minor
107
+ versions, but documented CLI inspection commands should remain the supported way
108
+ to read the store.
78
109
 
79
110
  Alpha users should pin an exact alpha version in package.json, CI, and MCP
80
111
  client snippets. Use `@alpha` only when intentionally testing the moving
@@ -0,0 +1,299 @@
1
+ # Runner Config Reference
2
+
3
+ `synapsor.runner.json` wires portable reviewed contracts to one local runtime.
4
+ It contains environment-variable names and fixed identifiers, never database
5
+ URLs, passwords, bearer tokens, prompts, or model-selected tenant authority.
6
+
7
+ Validate before serving:
8
+
9
+ ```bash
10
+ synapsor-runner config validate --config ./synapsor.runner.json
11
+ ```
12
+
13
+ Editor schema:
14
+
15
+ ```text
16
+ schemas/synapsor.runner.schema.json
17
+ ```
18
+
19
+ Unknown keys fail when `strict` is true (the default).
20
+
21
+ ## Top-level keys
22
+
23
+ | Key | Required | Meaning |
24
+ | --- | --- | --- |
25
+ | `version` | Yes | Must be `1`. |
26
+ | `mode` | Yes | `read_only`, `shadow`, `review`, or `cloud`. |
27
+ | `result_format` | No | `1` legacy or `2` stable `ok/summary/data/proposal/error` envelope. Default `1`. |
28
+ | `strict` | No | Reject unknown config keys. Default `true`. |
29
+ | `storage` | Local | Local SQLite ledger plus optional shared Postgres mirror or runtime-store wiring. |
30
+ | `sources` | Local | Named Postgres/MySQL source wiring. |
31
+ | `trusted_context` | Conditional | Default trusted tenant/principal binding. |
32
+ | `contexts` | No | Named trusted contexts referenced by capabilities/contracts. |
33
+ | `contracts` | No | Portable contract file paths. Preferred for reviewed authoring. |
34
+ | `capabilities` | Conditional | Embedded compatibility capabilities; may be `[]` with `contracts`. |
35
+ | `policies` | No | Embedded reviewed policies; contract policies merge into the same catalog. |
36
+ | `approvals` | No | Local approval overrides. |
37
+ | `operator_identity` | No | Verified operator identity and apply-role wiring for approve/reject/apply. |
38
+ | `executors` | No | App-owned writeback wiring. |
39
+ | `cloud` | Cloud mode | Scoped Cloud adapter configuration. |
40
+
41
+ ## Path resolution
42
+
43
+ - `contracts` entries resolve relative to the directory containing the config
44
+ file. Every command uses this rule, including audit, validate, doctor, tools,
45
+ smoke, MCP serve, propose, and apply.
46
+ - `storage.sqlite_path` is resolved by the Runner process working directory.
47
+ Pass an absolute path when an MCP client launches Runner from another CWD.
48
+ - Executor paths/URLs come from environment variables, not relative contract
49
+ content.
50
+
51
+ ## Storage
52
+
53
+ ```json
54
+ {
55
+ "storage": {
56
+ "sqlite_path": "./.synapsor/local.db",
57
+ "shared_postgres": {
58
+ "mode": "mirror",
59
+ "url_env": "SYNAPSOR_LEDGER_DATABASE_URL",
60
+ "schema": "synapsor_runner",
61
+ "lock_timeout_ms": 10000
62
+ }
63
+ }
64
+ }
65
+ ```
66
+
67
+ The ledger stores proposals, visible evidence copies, query audit, approvals,
68
+ receipts, events, and replay. New POSIX stores are owner-only (`0600`). Protect
69
+ the file like a database extract; see [Store Lifecycle](store-lifecycle.md).
70
+
71
+ `storage.shared_postgres.mode = "mirror"` makes mutating CLI commands restore
72
+ from the shared Postgres ledger before the local mutation and sync back after
73
+ the command while holding a schema-scoped Postgres advisory lock. Mirror mode is
74
+ a bounded operator handoff bridge; MCP serving still uses the local SQLite
75
+ store.
76
+
77
+ `storage.shared_postgres.mode = "runtime_store"` makes MCP serving use the
78
+ shared Postgres ledger as the primary proposal/evidence/replay store. Runner
79
+ creates a connection from the environment variable named by `url_env`, auto-runs
80
+ the shared-ledger migration at startup, and uses a schema-scoped transaction
81
+ advisory lock around runtime mutations. The URL is still referenced by
82
+ environment variable name; never put the database URL in the config file.
83
+ Bounded CLI approval/apply/worker commands bridge through the same Postgres
84
+ ledger by restoring into a temporary local store under the Postgres advisory
85
+ lock, running the existing mutation, and syncing back. Long-running worker
86
+ processes repeat bounded drain cycles and release the advisory lock while idle.
87
+
88
+ `synapsor-runner doctor --config ./synapsor.runner.json` checks shared Postgres
89
+ wiring when this block is present: the URL env var must be set, and the
90
+ configured schema must already contain `ledger_entries`, `proposal_locks`, and
91
+ `worker_leases`. Doctor reports env var names and table readiness only; it does
92
+ not print database URLs or create the schema.
93
+
94
+ ## Sources
95
+
96
+ ```json
97
+ {
98
+ "sources": {
99
+ "billing_postgres": {
100
+ "engine": "postgres",
101
+ "read_url_env": "BILLING_POSTGRES_READ_URL",
102
+ "write_url_env": "BILLING_POSTGRES_WRITE_URL",
103
+ "read_only": false,
104
+ "statement_timeout_ms": 3000
105
+ }
106
+ }
107
+ }
108
+ ```
109
+
110
+ `engine` is `postgres` or `mysql`. `read_url_env` is required. Use a
111
+ least-privilege read credential. `write_url_env` is optional unless direct SQL
112
+ writeback must apply; it should name a separate restricted writer. `read_only:
113
+ true` forbids direct SQL writeback. `statement_timeout_ms` is a positive read
114
+ timeout. `ssl` carries adapter-specific reviewed SSL options when used.
115
+
116
+ A contract's `SOURCE billing_postgres` must exactly match a `sources` key.
117
+
118
+ ## Trusted context
119
+
120
+ ```json
121
+ {
122
+ "trusted_context": {
123
+ "provider": "environment",
124
+ "values": {
125
+ "tenant_id_env": "SYNAPSOR_TENANT_ID",
126
+ "principal_env": "SYNAPSOR_PRINCIPAL"
127
+ }
128
+ }
129
+ }
130
+ ```
131
+
132
+ Providers are `environment`, `static_dev`, `http_claims`, and `cloud_session`.
133
+ `static_dev` is only for local fixtures. Named `contexts` use the same shape.
134
+ Capabilities may reference a context by name. The model never receives tenant
135
+ or principal as an overridable argument.
136
+
137
+ For multi-tenant Streamable HTTP services, use `http_claims` plus signed
138
+ session auth:
139
+
140
+ ```json
141
+ {
142
+ "trusted_context": {
143
+ "provider": "http_claims",
144
+ "values": {
145
+ "tenant_id_key": "tenant_id",
146
+ "principal_key": "sub"
147
+ }
148
+ },
149
+ "session_auth": {
150
+ "provider": "jwt_hs256",
151
+ "secret_env": "SYNAPSOR_SESSION_JWT_SECRET",
152
+ "previous_secret_env": "SYNAPSOR_PREVIOUS_SESSION_JWT_SECRET",
153
+ "issuer": "https://identity.example",
154
+ "audience": "synapsor-runner"
155
+ }
156
+ }
157
+ ```
158
+
159
+ `previous_secret_env` is optional and is only for rotation windows. Runner tries
160
+ the active secret first, then the previous secret. Existing MCP sessions remain
161
+ bound to the exact token fingerprint, so clients cannot swap tenant/principal
162
+ identity inside an initialized session.
163
+
164
+ ## Contracts and embedded capabilities
165
+
166
+ ```json
167
+ {
168
+ "contracts": ["./billing.contract.json"],
169
+ "capabilities": []
170
+ }
171
+ ```
172
+
173
+ Contracts are normalized `@synapsor/spec` JSON. Runner merges their contexts,
174
+ capabilities, and policies into one runtime catalog. Duplicate names across
175
+ embedded config and contracts fail; there is no silent shadowing.
176
+
177
+ Embedded capabilities remain supported for generated/legacy configs. Their
178
+ fields are documented in the JSON Schema and
179
+ [Capability Authoring](capability-authoring.md): fixed target, args, primary-key
180
+ lookup, visible fields, evidence, patch allowlist, bounds/transitions, conflict
181
+ guard, approval, and writeback mode.
182
+
183
+ ## Executors
184
+
185
+ ```json
186
+ {
187
+ "executors": {
188
+ "billing_handler": {
189
+ "type": "http_handler",
190
+ "url_env": "BILLING_HANDLER_URL",
191
+ "method": "POST",
192
+ "auth": {
193
+ "type": "bearer_env",
194
+ "token_env": "BILLING_HANDLER_TOKEN"
195
+ },
196
+ "signing_secret_env": "BILLING_HANDLER_SIGNING_SECRET",
197
+ "timeout_ms": 5000
198
+ }
199
+ }
200
+ }
201
+ ```
202
+
203
+ Types are `sql_update`, `http_handler`, and `command_handler`. HTTP handlers use
204
+ `url_env`, optional `POST|PUT|PATCH`, bearer auth, signing secret, and timeout.
205
+ Command handlers use `command_env` and timeout. Secrets stay in the environment.
206
+ See [Writeback Executors](writeback-executors.md).
207
+
208
+ ## Policies and approvals
209
+
210
+ `policies` is an array of reviewed policy objects. Local approval policies use
211
+ `kind: "approval"` and numeric rules such as `{ "field": "amount_cents",
212
+ "max": 2500 }`. Canonical policies may also carry daily `count` or `total`
213
+ limits scoped to `tenant_policy` or `tenant_policy_object`. These limits come
214
+ from reviewed DSL `LIMIT` clauses, not an unreviewed local override. A tripped
215
+ limit falls back to human review and is recorded in the ledger.
216
+ `approvals.disable_auto_approval: true` disables local policy auto-approval
217
+ without changing the reviewed contract. Approval never becomes an MCP tool.
218
+
219
+ ## Operator identity
220
+
221
+ Local development compatibility uses an explicitly unverified environment
222
+ identity:
223
+
224
+ ```json
225
+ {
226
+ "operator_identity": {
227
+ "provider": "dev_env",
228
+ "actor_env": "SYNAPSOR_OPERATOR_ID",
229
+ "roles_env": "SYNAPSOR_OPERATOR_ROLES",
230
+ "apply_roles": ["writeback_operator"]
231
+ }
232
+ }
233
+ ```
234
+
235
+ Shared or production-like operation should register signed operator keys:
236
+
237
+ ```json
238
+ {
239
+ "operator_identity": {
240
+ "provider": "signed_key",
241
+ "apply_roles": ["writeback_operator"],
242
+ "operators": {
243
+ "alice": {
244
+ "public_key_path": "./operators/alice.pub.pem",
245
+ "roles": ["support_reviewer", "writeback_operator"]
246
+ }
247
+ }
248
+ }
249
+ }
250
+ ```
251
+
252
+ The public key path resolves relative to `synapsor.runner.json`. Keep the
253
+ private key outside the repository and invoke decisions with `--identity alice
254
+ --identity-key /secure/path/alice.pem`. Runner verifies key possession and the
255
+ contract's required role, then binds the exact proposal hash/version, action,
256
+ subject, timestamp, roles, signature, and integrity hash into the approval
257
+ ledger. `apply_roles` independently gates writeback. The local browser UI
258
+ refuses approval/rejection in signed-key mode so it cannot bypass this check.
259
+
260
+ ## Cloud mode
261
+
262
+ Cloud mode omits local sources/capabilities and requires:
263
+
264
+ ```json
265
+ {
266
+ "version": 1,
267
+ "mode": "cloud",
268
+ "cloud": {
269
+ "base_url_env": "SYNAPSOR_CLOUD_BASE_URL",
270
+ "runner_token_env": "SYNAPSOR_RUNNER_TOKEN",
271
+ "adapter_id": "mcp.billing",
272
+ "runner_id": "runner_local_1",
273
+ "project_id": "project_123",
274
+ "engines": ["postgres"],
275
+ "capabilities": ["adapter:read", "adapter:invoke"],
276
+ "session": {}
277
+ }
278
+ }
279
+ ```
280
+
281
+ `base_url_env`, `runner_token_env`, and `adapter_id` are required. Optional keys
282
+ are `runner_id`, `runner_version`, `project_id`, `source_id`, `engines`, scoped
283
+ capability permissions, and trusted session metadata.
284
+
285
+ ## Direct SQL readiness
286
+
287
+ An administrator must apply `writeback migration` once. The steady-state writer
288
+ needs target-table SELECT/allowed UPDATE plus receipt-table
289
+ SELECT/INSERT/UPDATE; it does not need schema CREATE.
290
+
291
+ ```bash
292
+ synapsor-runner writeback migration --engine postgres --schema synapsor
293
+ synapsor-runner writeback grants --engine postgres \
294
+ --schema synapsor --writer-role synapsor_writer
295
+ synapsor-runner doctor --config ./synapsor.runner.json --check-writeback
296
+ ```
297
+
298
+ The doctor probe uses a rolled-back receipt insert and target-table check. It
299
+ does not mutate business rows.
@@ -66,11 +66,11 @@ outside the model-facing MCP server and verifies:
66
66
  If any authority check cannot be verified, the write fails closed.
67
67
 
68
68
  For direct SQL writeback, the writer connection is the env var named by the
69
- source `write_url_env` in `synapsor.runner.json`. Direct SQL writeback also
70
- creates or writes `synapsor_writeback_receipts` for idempotency and replay, so
71
- the writer needs permission for that receipt table or an administrator must
72
- pre-create and grant it. If your database policy forbids Runner-managed receipt
73
- tables, use an app-owned `http_handler` or `command_handler` executor instead.
69
+ source `write_url_env` in `synapsor.runner.json`. Direct SQL writeback writes an
70
+ administrator-created `synapsor_writeback_receipts` table for idempotency and
71
+ replay. The writer needs `SELECT`/`INSERT`/`UPDATE` on that table, but not schema
72
+ `CREATE`. If your database policy forbids the receipt table, use an app-owned
73
+ `http_handler` or `command_handler` executor instead.
74
74
 
75
75
  When a capability uses an `http_handler` or `command_handler` executor, the
76
76
  same approval boundary applies. The runner sends a structured proposal/job
@@ -9,6 +9,44 @@ Default path:
9
9
  ./.synapsor/local.db
10
10
  ```
11
11
 
12
+ ## Data sensitivity and permissions
13
+
14
+ The ledger contains copies of the business fields allowed by each capability:
15
+ visible before/after evidence, proposal diffs, actor/tenant metadata, query
16
+ fingerprints, approvals, receipts, and replay events. Treat it like a scoped
17
+ database extract, not disposable cache data.
18
+
19
+ Runner creates new POSIX store files with owner-only mode `0600` and tightens an
20
+ owned existing store when it opens it. On Windows, use the operating system's
21
+ file ACLs. Use encrypted disks, restrict OS accounts, and set a retention policy
22
+ appropriate for the visible business data.
23
+
24
+ `KEEP OUT` fields and redacted query parameters are not written to normal
25
+ evidence records. They do not make the rest of the ledger non-sensitive.
26
+
27
+ Direct SQLite inspection is supported **read-only** for independent verification:
28
+
29
+ ```bash
30
+ sqlite3 -readonly ./.synapsor/local.db '.tables'
31
+ ```
32
+
33
+ Do not mutate the database directly. Its internal tables may change between
34
+ releases and are not a public storage API; use Runner commands for automation.
35
+
36
+ ## Inspect the ledger
37
+
38
+ | Question | Command |
39
+ | --- | --- |
40
+ | What did the model propose? | `synapsor-runner proposals show <proposal-id> --details` |
41
+ | What data supported it? | `synapsor-runner evidence list --proposal <proposal-id>` then `evidence show <evidence-id> --details` |
42
+ | What query was run? | `synapsor-runner query-audit list --proposal <proposal-id>` |
43
+ | Who approved or rejected it? | `synapsor-runner proposals show <proposal-id> --details` |
44
+ | Did guarded writeback apply? | `synapsor-runner receipts list --proposal <proposal-id>` |
45
+ | What happened end to end? | `synapsor-runner replay show --proposal <proposal-id> --details` |
46
+ | What happened to one object? | `synapsor-runner activity search --object invoice:INV-3001` |
47
+ | What are the latest events? | `synapsor-runner events tail` |
48
+ | How large is the store? | `synapsor-runner store stats --store ./.synapsor/local.db` |
49
+
12
50
  ## Server leases
13
51
 
14
52
  MCP server modes write a small lease file next to the store:
@@ -65,9 +65,9 @@ npx -y -p @synapsor/runner synapsor-runner writeback grants --engine postgres --
65
65
  ```
66
66
 
67
67
  `writeback doctor --check-db` connects with the configured writer credential and
68
- checks the receipt table path. That check can create the receipt table if the
69
- writer has `CREATE`, so run it only against staging/disposable databases or
70
- after reviewing the printed migration/grants.
68
+ checks the pre-created receipt table path in a rolled-back transaction. Apply
69
+ the printed migration as an administrator first; the steady-state writer needs
70
+ table `SELECT`/`INSERT`/`UPDATE`, not schema `CREATE`.
71
71
 
72
72
  ## `http_handler`
73
73
 
@@ -40,6 +40,16 @@ CREATE TABLE IF NOT EXISTS public.account_credits (
40
40
  created_at timestamptz NOT NULL DEFAULT now()
41
41
  );
42
42
 
43
+ CREATE TABLE IF NOT EXISTS public.synapsor_writeback_receipts (
44
+ idempotency_key text PRIMARY KEY,
45
+ job_id text UNIQUE NOT NULL,
46
+ proposal_id text NOT NULL,
47
+ status text NOT NULL,
48
+ result_hash text,
49
+ created_at timestamptz NOT NULL DEFAULT now(),
50
+ completed_at timestamptz
51
+ );
52
+
43
53
  DO $$
44
54
  BEGIN
45
55
  IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'synapsor_reader') THEN
@@ -53,7 +63,7 @@ $$;
53
63
 
54
64
  GRANT CONNECT ON DATABASE synapsor_billing_app_handler TO synapsor_reader, synapsor_writer;
55
65
  GRANT USAGE ON SCHEMA public TO synapsor_reader, synapsor_writer;
56
- GRANT CREATE ON SCHEMA public TO synapsor_writer;
57
66
  GRANT SELECT ON public.tenants, public.customers, public.invoices, public.account_credits TO synapsor_reader, synapsor_writer;
67
+ GRANT SELECT, INSERT, UPDATE ON public.synapsor_writeback_receipts TO synapsor_writer;
58
68
  GRANT UPDATE (late_fee_cents, waiver_reason, credit_requested_cents, credit_reason, credited_cents, updated_at) ON public.invoices TO synapsor_writer;
59
69
  GRANT INSERT ON public.account_credits TO synapsor_writer;
@@ -47,6 +47,16 @@ CREATE TABLE IF NOT EXISTS public.orders (
47
47
  updated_at timestamptz NOT NULL DEFAULT now()
48
48
  );
49
49
 
50
+ CREATE TABLE IF NOT EXISTS public.synapsor_writeback_receipts (
51
+ idempotency_key text PRIMARY KEY,
52
+ job_id text UNIQUE NOT NULL,
53
+ proposal_id text NOT NULL,
54
+ status text NOT NULL,
55
+ result_hash text,
56
+ created_at timestamptz NOT NULL DEFAULT now(),
57
+ completed_at timestamptz
58
+ );
59
+
50
60
  DO $$
51
61
  BEGIN
52
62
  IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'synapsor_reader') THEN
@@ -60,8 +70,8 @@ $$;
60
70
 
61
71
  GRANT CONNECT ON DATABASE synapsor_reference_support_billing TO synapsor_reader, synapsor_writer;
62
72
  GRANT USAGE ON SCHEMA public TO synapsor_reader, synapsor_writer;
63
- GRANT CREATE ON SCHEMA public TO synapsor_writer;
64
73
  GRANT SELECT ON public.tenants, public.customers, public.support_tickets, public.invoices, public.orders TO synapsor_reader, synapsor_writer;
74
+ GRANT SELECT, INSERT, UPDATE ON public.synapsor_writeback_receipts TO synapsor_writer;
65
75
  GRANT UPDATE (plan_credit_cents, credit_reason, updated_at) ON public.customers TO synapsor_writer;
66
76
  GRANT UPDATE (status, resolution_note, updated_at) ON public.support_tickets TO synapsor_writer;
67
77
  GRANT UPDATE (late_fee_cents, waiver_reason, updated_at) ON public.invoices TO synapsor_writer;
@@ -70,6 +70,16 @@ CREATE TABLE IF NOT EXISTS public.orders (
70
70
  updated_at timestamptz NOT NULL DEFAULT now()
71
71
  );
72
72
 
73
+ CREATE TABLE IF NOT EXISTS public.synapsor_writeback_receipts (
74
+ idempotency_key text PRIMARY KEY,
75
+ job_id text UNIQUE NOT NULL,
76
+ proposal_id text NOT NULL,
77
+ status text NOT NULL,
78
+ result_hash text,
79
+ created_at timestamptz NOT NULL DEFAULT now(),
80
+ completed_at timestamptz
81
+ );
82
+
73
83
  DO $$
74
84
  BEGIN
75
85
  IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'synapsor_reader') THEN
@@ -83,8 +93,8 @@ $$;
83
93
 
84
94
  GRANT CONNECT ON DATABASE synapsor_support_billing_agent TO synapsor_reader, synapsor_writer;
85
95
  GRANT USAGE ON SCHEMA public TO synapsor_reader, synapsor_writer;
86
- GRANT CREATE ON SCHEMA public TO synapsor_writer;
87
96
  GRANT SELECT ON public.tenants, public.customers, public.support_tickets, public.invoices, public.credits, public.agent_actions, public.orders TO synapsor_reader, synapsor_writer;
97
+ GRANT SELECT, INSERT, UPDATE ON public.synapsor_writeback_receipts TO synapsor_writer;
88
98
  GRANT UPDATE (plan_credit_cents, credit_reason, updated_at) ON public.customers TO synapsor_writer;
89
99
  GRANT UPDATE (status, resolution_note, updated_at) ON public.support_tickets TO synapsor_writer;
90
100
  GRANT UPDATE (late_fee_cents, waiver_reason, updated_at) ON public.invoices TO synapsor_writer;
@@ -196,10 +196,15 @@ The auto-approval policy lives in `contract.synapsor.sql`:
196
196
 
197
197
  ```sql
198
198
  AUTO APPROVE WHEN plan_credit_cents <= 2500
199
+ LIMIT 20 PER DAY
200
+ LIMIT TOTAL 100000 PER DAY
199
201
  ```
200
202
 
201
203
  That compiles into `synapsor.contract.json` as a reviewed approval policy. It is
202
- stored in git, validated by `@synapsor/spec`, and included in Cloud push payloads.
204
+ stored in git, validated by `@synapsor/spec`, and included in Cloud push
205
+ payloads. The policy may approve at most 20 qualifying credits or 100,000 cents
206
+ in total per trusted tenant and UTC day. The first ceiling reached leaves later
207
+ proposals in `pending_review` and records why the policy deferred to a human.
203
208
 
204
209
  To disable local policy approval without changing the contract:
205
210
 
@@ -214,6 +219,16 @@ To disable local policy approval without changing the contract:
214
219
  Auto-approval never applies the write. It only records an approval row and audit
215
220
  event with actor `policy:<policy_name>`.
216
221
 
222
+ After reviewing the ceilings and running `doctor`, an operator can drain the
223
+ approved queue without letting one stale-row conflict abort the remaining jobs:
224
+
225
+ ```bash
226
+ synapsor-runner apply --all-approved --yes \
227
+ --capability support.propose_plan_credit --tenant acme --max 20 \
228
+ --config examples/support-plan-credit/synapsor.runner.json \
229
+ --store ./tmp/support-plan-credit/local.db
230
+ ```
231
+
217
232
  ## Cloud
218
233
 
219
234
  Dry-run the Cloud payload:
@@ -46,6 +46,8 @@ CREATE CAPABILITY support.propose_plan_credit
46
46
  BOUND plan_credit_cents 1..50000
47
47
  APPROVAL ROLE support_reviewer
48
48
  AUTO APPROVE WHEN plan_credit_cents <= 2500
49
+ LIMIT 20 PER DAY
50
+ LIMIT TOTAL 100000 PER DAY
49
51
  WRITEBACK DIRECT SQL
50
52
  END
51
53