@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.
- package/CHANGELOG.md +48 -0
- package/README.md +25 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/runner.mjs +3796 -1063
- package/docs/README.md +4 -0
- package/docs/capability-authoring.md +5 -0
- package/docs/conformance.md +8 -0
- package/docs/current-scope.md +2 -2
- package/docs/doctor.md +3 -3
- package/docs/dsl-reference.md +215 -0
- package/docs/http-mcp.md +41 -0
- package/docs/limitations.md +2 -1
- package/docs/oss-vs-cloud.md +3 -2
- package/docs/production.md +178 -9
- package/docs/release-notes.md +52 -9
- package/docs/release-policy.md +37 -6
- package/docs/runner-config-reference.md +299 -0
- package/docs/security-boundary.md +5 -5
- package/docs/store-lifecycle.md +38 -0
- package/docs/writeback-executors.md +3 -3
- package/examples/mcp-postgres-billing-app-handler/schema.sql +11 -1
- package/examples/reference-support-billing-app/schema.sql +11 -1
- package/examples/support-billing-agent/db/schema.sql +11 -1
- package/examples/support-plan-credit/README.md +16 -1
- package/examples/support-plan-credit/contract.synapsor.sql +2 -0
- package/examples/support-plan-credit/seed/001_seed.sql +11 -1
- package/examples/support-plan-credit/synapsor.contract.json +15 -0
- package/package.json +1 -1
- package/schemas/synapsor.runner.schema.json +216 -3
package/docs/release-notes.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Release Notes
|
|
2
2
|
|
|
3
|
-
These notes track public Synapsor Runner behavior. Starting with `
|
|
4
|
-
|
|
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
|
|
402
|
-
|
|
403
|
-
|
|
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
|
|
461
|
-
|
|
462
|
-
|
|
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
|
|
package/docs/release-policy.md
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
# Release Policy
|
|
2
2
|
|
|
3
|
-
Synapsor Runner `0.1.0`
|
|
4
|
-
|
|
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 `
|
|
61
|
-
through the `
|
|
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 `
|
|
77
|
-
inspection commands should remain the supported way
|
|
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
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
package/docs/store-lifecycle.md
CHANGED
|
@@ -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
|
|
69
|
-
|
|
70
|
-
|
|
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
|
|
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:
|