@synapsor/runner 1.5.3 → 1.5.4
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 +44 -2
- package/README.md +20 -12
- package/dist/cli.d.ts.map +1 -1
- package/dist/runner.mjs +2332 -501
- package/dist/runtime.mjs +788 -148
- package/dist/shadow.mjs +101 -1
- package/docs/capability-authoring.md +6 -1
- package/docs/cloud-mode.md +18 -0
- package/docs/database-enforced-scope.md +8 -0
- package/docs/dsl-reference.md +45 -4
- package/docs/getting-started-own-database.md +6 -3
- package/docs/guarded-crud-writeback.md +10 -1
- package/docs/http-mcp.md +222 -207
- package/docs/limitations.md +9 -2
- package/docs/local-mode.md +5 -2
- package/docs/mcp-client-setup.md +24 -11
- package/docs/mcp-clients.md +10 -4
- package/docs/openai-agents-sdk.md +16 -2
- package/docs/production.md +72 -7
- package/docs/release-notes.md +44 -3
- package/docs/runner-bundles.md +7 -2
- package/docs/runner-config-reference.md +96 -6
- package/docs/running-a-runner-fleet.md +42 -13
- package/docs/security-boundary.md +38 -3
- package/docs/store-lifecycle.md +93 -5
- package/examples/openai-agents-http/README.md +10 -4
- package/examples/openai-agents-http/agent.py +2 -2
- package/examples/runner-fleet/Dockerfile +7 -2
- package/examples/runner-fleet/README.md +11 -7
- package/examples/runner-fleet/docker-compose.yml +4 -4
- package/examples/runner-fleet/mint-dev-token.mjs +1 -1
- package/examples/runner-fleet/start-tls-runner.sh +21 -0
- package/examples/runner-fleet/synapsor.runner.json +27 -1
- package/examples/support-plan-credit/README.md +6 -1
- package/examples/support-plan-credit/mcp-client-examples/generic-streamable-http.json +4 -1
- package/examples/support-plan-credit/mcp-client-examples/openai-agents-streamable-http.ts +4 -0
- package/examples/support-plan-credit/synapsor.contract.json +0 -3
- package/package.json +1 -1
- package/schemas/synapsor.runner.schema.json +80 -0
package/dist/shadow.mjs
CHANGED
|
@@ -1908,6 +1908,11 @@ var ProposalStore = class {
|
|
|
1908
1908
|
const rows = this.db.prepare(query.sql).all(...query.params);
|
|
1909
1909
|
return rows.map((row) => rowToProposal(row)).filter((proposal) => proposal !== void 0);
|
|
1910
1910
|
}
|
|
1911
|
+
countProposals(filters = {}) {
|
|
1912
|
+
const query = buildProposalCountQuery(filters);
|
|
1913
|
+
const row = this.db.prepare(query.sql).get(...query.params);
|
|
1914
|
+
return isRecord(row) ? Number(row.count ?? 0) : 0;
|
|
1915
|
+
}
|
|
1911
1916
|
listEvidenceBundles(filters = {}) {
|
|
1912
1917
|
const query = buildEvidenceQuery(filters);
|
|
1913
1918
|
const rows = this.db.prepare(query.sql).all(...query.params);
|
|
@@ -1934,6 +1939,19 @@ var ProposalStore = class {
|
|
|
1934
1939
|
const proposalId = replayId.startsWith(prefix) ? replayId.slice(prefix.length) : replayId;
|
|
1935
1940
|
return this.replay(proposalId);
|
|
1936
1941
|
}
|
|
1942
|
+
getStoredReplay(replayId) {
|
|
1943
|
+
const row = this.db.prepare("SELECT * FROM replay_records WHERE replay_id = ?").get(replayId);
|
|
1944
|
+
return rowToStoredReplay(row);
|
|
1945
|
+
}
|
|
1946
|
+
getStoredReplayForProposal(proposalId) {
|
|
1947
|
+
const row = this.db.prepare(`
|
|
1948
|
+
SELECT * FROM replay_records
|
|
1949
|
+
WHERE proposal_id = ?
|
|
1950
|
+
ORDER BY created_at DESC, replay_id DESC
|
|
1951
|
+
LIMIT 1
|
|
1952
|
+
`).get(proposalId);
|
|
1953
|
+
return rowToStoredReplay(row);
|
|
1954
|
+
}
|
|
1937
1955
|
proposalIdForEvidence(evidenceBundleId) {
|
|
1938
1956
|
const evidence = this.getEvidenceBundle(evidenceBundleId);
|
|
1939
1957
|
if (evidence?.proposal_id) return evidence.proposal_id;
|
|
@@ -2236,6 +2254,19 @@ var ProposalStore = class {
|
|
|
2236
2254
|
});
|
|
2237
2255
|
return job;
|
|
2238
2256
|
}
|
|
2257
|
+
getWritebackJob(writebackJobId) {
|
|
2258
|
+
return rowToWritebackJob(this.db.prepare("SELECT * FROM writeback_jobs WHERE writeback_job_id = ?").get(writebackJobId));
|
|
2259
|
+
}
|
|
2260
|
+
listWritebackJobs(options = {}) {
|
|
2261
|
+
const clauses = [];
|
|
2262
|
+
const values = [];
|
|
2263
|
+
if (options.proposal_id) {
|
|
2264
|
+
clauses.push("proposal_id = ?");
|
|
2265
|
+
values.push(options.proposal_id);
|
|
2266
|
+
}
|
|
2267
|
+
const limit = Math.min(Math.max(options.limit ?? 100, 1), 1e3);
|
|
2268
|
+
return this.db.prepare(`SELECT * FROM writeback_jobs${clauses.length ? ` WHERE ${clauses.join(" AND ")}` : ""} ORDER BY created_at ASC, writeback_job_id ASC LIMIT ?`).all(...values, limit).map(rowToWritebackJob).filter((job) => Boolean(job));
|
|
2269
|
+
}
|
|
2239
2270
|
claimWritebackIntent(jobInput, runnerId) {
|
|
2240
2271
|
const job = parseWritebackJob(jobInput);
|
|
2241
2272
|
const proposal = this.requireProposal(job.proposal_id);
|
|
@@ -4059,6 +4090,19 @@ function inWhere(column, values) {
|
|
|
4059
4090
|
};
|
|
4060
4091
|
}
|
|
4061
4092
|
function buildProposalQuery(filters) {
|
|
4093
|
+
const { clauses, params } = proposalQueryParts(filters);
|
|
4094
|
+
const where = clauses.length ? ` WHERE ${clauses.join(" AND ")}` : "";
|
|
4095
|
+
return {
|
|
4096
|
+
sql: `SELECT * FROM proposals${where} ORDER BY created_at DESC, proposal_id DESC${filters.limit ? " LIMIT ?" : ""}`,
|
|
4097
|
+
params: filters.limit ? [...params, filters.limit] : params
|
|
4098
|
+
};
|
|
4099
|
+
}
|
|
4100
|
+
function buildProposalCountQuery(filters) {
|
|
4101
|
+
const { clauses, params } = proposalQueryParts(filters);
|
|
4102
|
+
const where = clauses.length ? ` WHERE ${clauses.join(" AND ")}` : "";
|
|
4103
|
+
return { sql: `SELECT COUNT(*) AS count FROM proposals${where}`, params };
|
|
4104
|
+
}
|
|
4105
|
+
function proposalQueryParts(filters) {
|
|
4062
4106
|
const clauses = [];
|
|
4063
4107
|
const params = [];
|
|
4064
4108
|
addEqual(clauses, params, "proposal_id", filters.proposal);
|
|
@@ -4070,7 +4114,7 @@ function buildProposalQuery(filters) {
|
|
|
4070
4114
|
addEqual(clauses, params, "action", filters.capability ?? filters.action);
|
|
4071
4115
|
addObjectFilter(clauses, params, "business_object", "source_table", "object_id", filters.objectType, filters.objectId);
|
|
4072
4116
|
addTimeRange(clauses, params, "created_at", filters.from, filters.to);
|
|
4073
|
-
return
|
|
4117
|
+
return { clauses, params };
|
|
4074
4118
|
}
|
|
4075
4119
|
function buildEvidenceQuery(filters) {
|
|
4076
4120
|
const clauses = [];
|
|
@@ -4652,6 +4696,62 @@ function rowToReceipt(row) {
|
|
|
4652
4696
|
source_table: row.source_table == null ? void 0 : String(row.source_table)
|
|
4653
4697
|
};
|
|
4654
4698
|
}
|
|
4699
|
+
function rowToWritebackJob(row) {
|
|
4700
|
+
if (!isRecord(row)) return void 0;
|
|
4701
|
+
let payload;
|
|
4702
|
+
try {
|
|
4703
|
+
payload = JSON.parse(String(row.job_json));
|
|
4704
|
+
} catch {
|
|
4705
|
+
throw new ProposalStoreError("WRITEBACK_JOB_CORRUPT", `writeback job ${String(row.writeback_job_id)} payload is not valid JSON`);
|
|
4706
|
+
}
|
|
4707
|
+
if (!isRecord(payload)) {
|
|
4708
|
+
throw new ProposalStoreError("WRITEBACK_JOB_CORRUPT", `writeback job ${String(row.writeback_job_id)} payload is not an object`);
|
|
4709
|
+
}
|
|
4710
|
+
const handler = payload.schema_version === "synapsor.handler-writeback.v1";
|
|
4711
|
+
let normalizedJob;
|
|
4712
|
+
if (!handler) {
|
|
4713
|
+
try {
|
|
4714
|
+
normalizedJob = parseWritebackJob(payload);
|
|
4715
|
+
} catch {
|
|
4716
|
+
throw new ProposalStoreError("WRITEBACK_JOB_CORRUPT", `writeback job ${String(row.writeback_job_id)} payload is not a supported writeback protocol`);
|
|
4717
|
+
}
|
|
4718
|
+
}
|
|
4719
|
+
const payloadJobId = handler ? payload.writeback_job_id : normalizedJob?.job_id;
|
|
4720
|
+
const payloadProposalId = handler ? payload.proposal_id : normalizedJob?.proposal_id;
|
|
4721
|
+
const payloadProposalHash = handler ? payload.proposal_hash : normalizedJob?.approval_id;
|
|
4722
|
+
if (payloadJobId !== String(row.writeback_job_id) || payloadProposalId !== String(row.proposal_id) || payloadProposalHash !== String(row.proposal_hash)) {
|
|
4723
|
+
throw new ProposalStoreError("WRITEBACK_JOB_CORRUPT", `writeback job ${String(row.writeback_job_id)} index fields do not match its payload`);
|
|
4724
|
+
}
|
|
4725
|
+
return {
|
|
4726
|
+
writeback_job_id: String(row.writeback_job_id),
|
|
4727
|
+
proposal_id: String(row.proposal_id),
|
|
4728
|
+
proposal_hash: String(row.proposal_hash),
|
|
4729
|
+
status: String(row.status),
|
|
4730
|
+
kind: handler ? "app_handler" : "direct_sql",
|
|
4731
|
+
payload,
|
|
4732
|
+
...normalizedJob ? { normalized_job: normalizedJob } : {},
|
|
4733
|
+
created_at: String(row.created_at),
|
|
4734
|
+
updated_at: String(row.updated_at)
|
|
4735
|
+
};
|
|
4736
|
+
}
|
|
4737
|
+
function rowToStoredReplay(row) {
|
|
4738
|
+
if (!isRecord(row)) return void 0;
|
|
4739
|
+
let payload;
|
|
4740
|
+
try {
|
|
4741
|
+
payload = JSON.parse(String(row.payload_json));
|
|
4742
|
+
} catch {
|
|
4743
|
+
throw new ProposalStoreError("REPLAY_RECORD_CORRUPT", `replay ${String(row.replay_id)} payload is not valid JSON`);
|
|
4744
|
+
}
|
|
4745
|
+
if (!isRecord(payload) || !isRecord(payload.proposal)) {
|
|
4746
|
+
throw new ProposalStoreError("REPLAY_RECORD_CORRUPT", `replay ${String(row.replay_id)} payload is not a supported replay record`);
|
|
4747
|
+
}
|
|
4748
|
+
const replayId = String(row.replay_id);
|
|
4749
|
+
const proposalId = String(row.proposal_id);
|
|
4750
|
+
if (payload.replay_id !== replayId || payload.proposal.proposal_id !== proposalId || !Array.isArray(payload.approvals) || !Array.isArray(payload.events) || !Array.isArray(payload.receipts) || !Array.isArray(payload.query_audit) || !Array.isArray(payload.evidence) || typeof payload.generated_at !== "string") {
|
|
4751
|
+
throw new ProposalStoreError("REPLAY_RECORD_CORRUPT", `replay ${replayId} index fields do not match its payload`);
|
|
4752
|
+
}
|
|
4753
|
+
return payload;
|
|
4754
|
+
}
|
|
4655
4755
|
function rowToWritebackIntent(row) {
|
|
4656
4756
|
if (!isRecord(row)) return void 0;
|
|
4657
4757
|
const status = String(row.status);
|
|
@@ -196,7 +196,8 @@ reviewed runner JSON capabilities. Current parity:
|
|
|
196
196
|
| arg `enum` | `ARG risk_level STRING ENUM('low', 'medium', 'high') REQUIRED` | 1.4.121 | Supports 1..64 same-type string, number, or boolean values; duplicates and mixed types fail compilation. |
|
|
197
197
|
| proposal `numeric_bounds` | `BOUND column 1..2500`, `BOUND column ..2500`, or `BOUND column 1..` | 0.1.8 | Applies to patched numeric columns. Strict mode warns when a NUMBER arg is patched without arg min/max or a matching `BOUND`. |
|
|
198
198
|
| proposal `transition_guards` | `TRANSITION status ALLOW pending -> approved\|rejected` or `TRANSITION status FROM current_status ALLOW open -> closed` | 0.1.8 | Values are state strings; use `|` for multiple target states. |
|
|
199
|
-
| proposal `conflict_guard` | `CONFLICT GUARD updated_at` | 0.1 |
|
|
199
|
+
| proposal `conflict_guard` | `CONFLICT GUARD updated_at` | 0.1 | Required for ordinary UPDATE authoring. It compiles to an exact version-column check. Omission fails instead of silently weakening concurrency protection. |
|
|
200
|
+
| proposal weak `conflict_guard` | `CONFLICT GUARD WEAK ROW HASH ACKNOWLEDGED` | 1.4.4 | Explicit legacy escape hatch for an ordinary single-row source-DB UPDATE only. It hashes the captured projection and may miss concurrent changes outside that projection. It is rejected for DELETE, reversible writes, bounded sets, and Runner-ledger authority. |
|
|
200
201
|
| proposal `approval` | `APPROVAL ROLE billing_lead` | 0.1 | Local mode records the required role; enforcement is still outside the model-facing MCP tool. |
|
|
201
202
|
| proposal `approval.required_approvals` | `REQUIRE 2 APPROVALS` | 1.1 | Optional 1..10 distinct-reviewer quorum; defaults to 1. |
|
|
202
203
|
| proposal `writeback` | `WRITEBACK DIRECT SQL`, `WRITEBACK APP HANDLER EXECUTOR name`, `WRITEBACK CLOUD WORKER`, `WRITEBACK NONE` | 0.1.7 | Handler URLs/tokens stay in `synapsor.runner.json`; contracts carry only the handler name. |
|
|
@@ -523,6 +524,8 @@ Canonical Synapsor names use dots, such as `billing.inspect_invoice`. Some
|
|
|
523
524
|
clients require function-safe names. Use:
|
|
524
525
|
|
|
525
526
|
```bash
|
|
527
|
+
export SYNAPSOR_RUNNER_HTTP_TOKEN="$(node -e 'process.stdout.write(require("node:crypto").randomBytes(32).toString("base64url"))')"
|
|
528
|
+
|
|
526
529
|
synapsor-runner mcp serve-streamable-http \
|
|
527
530
|
--config ./synapsor.runner.json \
|
|
528
531
|
--store ./.synapsor/local.db \
|
|
@@ -533,6 +536,8 @@ synapsor-runner mcp serve-streamable-http \
|
|
|
533
536
|
The model sees aliases such as `billing__inspect_invoice`. Runner includes the
|
|
534
537
|
canonical name in tool metadata and descriptions so audit/replay still use the
|
|
535
538
|
real capability name.
|
|
539
|
+
This loopback token is opaque endpoint access, not tenant identity. See
|
|
540
|
+
[HTTP MCP](http-mcp.md) before using a network listener.
|
|
536
541
|
|
|
537
542
|
## Why Not `execute_sql`
|
|
538
543
|
|
package/docs/cloud-mode.md
CHANGED
|
@@ -19,6 +19,24 @@ The local Runner still keeps database credentials in your environment. MCP
|
|
|
19
19
|
client config snippets contain command paths and Runner arguments, not database
|
|
20
20
|
URLs or write credentials.
|
|
21
21
|
|
|
22
|
+
Keep Cloud and MCP credentials distinct:
|
|
23
|
+
|
|
24
|
+
| Credential | Purpose | Issuer/provisioner | Never substitutes for |
|
|
25
|
+
| --- | --- | --- | --- |
|
|
26
|
+
| Cloud user session | Human control-panel/CLI identity and subscription/RBAC checks | Synapsor Cloud identity system | MCP tenant identity or database access |
|
|
27
|
+
| Cloud service/API key | Contract push and scoped Cloud administration | Synapsor Cloud | MCP endpoint access |
|
|
28
|
+
| Source-scoped Runner token | Runner registration, heartbeat, lease, activity, and result calls for one Cloud source | Synapsor Cloud Connect Runner flow | Model-facing MCP access or operator approval |
|
|
29
|
+
| Opaque MCP endpoint token | Loopback or explicitly single-tenant HTTP endpoint access | Customer operator/secret manager | User/tenant identity |
|
|
30
|
+
| Signed MCP access JWT | Shared HTTP client identity and verified tenant/principal claims | Customer identity provider/authorization server | Cloud subscription credential or DB role |
|
|
31
|
+
| Operator credential | Activation, approval, apply, reconcile, dead-letter, and revert authority | Customer operator identity system | Model-facing MCP capability |
|
|
32
|
+
| Database/handler credential | Local read or trusted post-approval effect | Customer secret manager/database/application | Cloud or MCP authentication |
|
|
33
|
+
|
|
34
|
+
Cloud-linked mode does not require networked MCP: a desktop client can still
|
|
35
|
+
launch the local Runner over stdio. If the local Runner exposes Streamable HTTP,
|
|
36
|
+
the same [HTTP MCP](http-mcp.md) channel and identity profiles apply. Cloud API
|
|
37
|
+
keys and source-scoped Runner tokens are never accepted as shortcuts for MCP
|
|
38
|
+
session authentication.
|
|
39
|
+
|
|
22
40
|
## Trust Boundary
|
|
23
41
|
|
|
24
42
|
```text
|
|
@@ -11,6 +11,10 @@ from model arguments. Choose the database enforcement mode deliberately:
|
|
|
11
11
|
|
|
12
12
|
These modes are defense in depth, not substitutes for least-privilege roles,
|
|
13
13
|
restricted views, application authorization, or staging-first validation.
|
|
14
|
+
HTTP endpoint authentication is another independent layer: it establishes who
|
|
15
|
+
may call Runner and, for signed shared sessions, which trusted tenant/principal
|
|
16
|
+
claims are bound. It does not turn `application_scope` into database-enforced
|
|
17
|
+
isolation. See [HTTP MCP](http-mcp.md).
|
|
14
18
|
|
|
15
19
|
## Default: Application-Level Scope
|
|
16
20
|
|
|
@@ -49,6 +53,10 @@ with environment-bound capabilities.
|
|
|
49
53
|
For shared production HTTP, prefer asymmetric JWT verification so Runner holds
|
|
50
54
|
only public verification material. HS256 remains useful for local development
|
|
51
55
|
and controlled deployments but gives Runner access to the signing secret.
|
|
56
|
+
Also declare `http_security.deployment: shared`, an exact HTTPS
|
|
57
|
+
audience/protected resource, a direct-TLS or trusted-proxy channel, and exact
|
|
58
|
+
Host/Origin policy. A static endpoint token cannot supply shared tenant or
|
|
59
|
+
principal identity.
|
|
52
60
|
|
|
53
61
|
Run `doctor --json` or `tools preview --json` to inspect the effective
|
|
54
62
|
per-source assurance mode and trusted-context binding. Server startup prints
|
package/docs/dsl-reference.md
CHANGED
|
@@ -29,7 +29,7 @@ END
|
|
|
29
29
|
|
|
30
30
|
| Clause | Meaning |
|
|
31
31
|
| --- | --- |
|
|
32
|
-
| `BIND name FROM source key [REQUIRED]` | Defines trusted context.
|
|
32
|
+
| `BIND name FROM source key [REQUIRED]` | Defines trusted context. Canonical sources: `SESSION`, `ENV`/`ENVIRONMENT`, `CLOUD_SESSION`, `STATIC_DEV`, `HTTP_CLAIM`. Model tool arguments cannot set these bindings. Runner rejects `SESSION`; use one of its implemented verified providers described below. |
|
|
33
33
|
| `TENANT BINDING name` | Selects the binding used for tenant scope. Defaults to a binding named `tenant_id`. |
|
|
34
34
|
| `PRINCIPAL BINDING name` | Selects the actor binding. Defaults to a binding named `principal`. |
|
|
35
35
|
|
|
@@ -65,16 +65,57 @@ END
|
|
|
65
65
|
| `TENANT KEY column` | Required in DSL 0.1. Adds trusted tenant scope to every read/write. |
|
|
66
66
|
| `PRINCIPAL SCOPE KEY column` | Optional tenant-additive row lock. Runner binds this fixed column to the context's required trusted `PRINCIPAL BINDING`; it is never a model argument. |
|
|
67
67
|
| `CONFLICT GUARD column` | Captures the row-version value for exact guarded writeback. Prefer a monotonic version or native-precision timestamp. |
|
|
68
|
+
| `CONFLICT GUARD WEAK ROW HASH ACKNOWLEDGED` | Explicitly accepts a weaker hash over the captured projection for a legacy, ordinary single-row source-DB UPDATE. It may miss concurrent changes outside that projection and is never equivalent to a version column. |
|
|
69
|
+
|
|
70
|
+
An UPDATE proposal must choose one of those guard clauses. Omitting the clause
|
|
71
|
+
is a compile error; the compiler never silently selects the weak form. The weak
|
|
72
|
+
form is rejected for INSERT, DELETE, reversible writes, bounded sets, and
|
|
73
|
+
Runner-ledger authority. `contract lint`, `contract explain`, `doctor`, and
|
|
74
|
+
`tools preview` all identify it prominently.
|
|
75
|
+
|
|
76
|
+
To migrate a legacy DSL file that omitted the guard, inspect the target schema
|
|
77
|
+
and add an exact source column:
|
|
78
|
+
|
|
79
|
+
```sql
|
|
80
|
+
CONFLICT GUARD version
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Use a monotonic integer/version column when available, or a native-precision
|
|
84
|
+
`updated_at` maintained by the database. If a legacy source has no usable
|
|
85
|
+
version column and the capability is an ordinary single-row UPDATE using
|
|
86
|
+
source-DB receipt authority, the explicit weak clause keeps the previous
|
|
87
|
+
projection-hash behavior while recording the reduced assurance in review
|
|
88
|
+
output. Do not use it as a production-equivalent substitute for a real version
|
|
89
|
+
column.
|
|
68
90
|
|
|
69
91
|
`PRINCIPAL SCOPE KEY` means `tenant_key = trusted tenant AND
|
|
70
92
|
principal_scope_key = trusted principal`. It cannot replace tenant scope,
|
|
71
93
|
default to all rows, or be overridden by an argument. The principal binding
|
|
72
|
-
must be required and come from a trusted provider
|
|
73
|
-
`HTTP_CLAIM`, `
|
|
74
|
-
|
|
94
|
+
must be required and come from a trusted Runner provider: `ENVIRONMENT`,
|
|
95
|
+
verified `HTTP_CLAIM`, verified `CLOUD_SESSION`, or explicit
|
|
96
|
+
`STATIC_DEV` for development only. For networked multi-user serving, use
|
|
97
|
+
signed HTTP claims. Canonical `SESSION` is implemented by C++/Cloud but is
|
|
98
|
+
rejected by Runner; Runner never aliases it to environment variables. See the complete
|
|
75
99
|
[`principal-row-scope.synapsor.sql`](../fixtures/dsl/principal-row-scope.synapsor.sql)
|
|
76
100
|
example.
|
|
77
101
|
|
|
102
|
+
### Migrating `FROM SESSION` for Runner
|
|
103
|
+
|
|
104
|
+
Choose the provider that owns the verified identity at the Runner boundary:
|
|
105
|
+
|
|
106
|
+
- local stdio or one trusted process: `FROM ENVIRONMENT`; export the named
|
|
107
|
+
values before starting Runner;
|
|
108
|
+
- Streamable HTTP: `FROM HTTP_CLAIM`; configure signed JWT verification and
|
|
109
|
+
bind only verified claims;
|
|
110
|
+
- Cloud embedding: `FROM CLOUD_SESSION`; use the verified Cloud session
|
|
111
|
+
supplied by the control plane; or
|
|
112
|
+
- disposable development only: `FROM STATIC_DEV`.
|
|
113
|
+
|
|
114
|
+
`synapsor-runner dsl validate` and `dsl compile` target Runner and fail with
|
|
115
|
+
`SESSION_BINDING_UNSUPPORTED` before serving. The standalone DSL accepts the
|
|
116
|
+
canonical syntax by default for other implementations; pass `--target runner`
|
|
117
|
+
to request the same fail-closed Runner validation.
|
|
118
|
+
|
|
78
119
|
## Arguments and lookup
|
|
79
120
|
|
|
80
121
|
```sql
|
|
@@ -520,7 +520,7 @@ MCP client:
|
|
|
520
520
|
```bash
|
|
521
521
|
export SYNAPSOR_TENANT_ID="acme"
|
|
522
522
|
export SYNAPSOR_PRINCIPAL="local_operator"
|
|
523
|
-
export SYNAPSOR_RUNNER_HTTP_TOKEN="
|
|
523
|
+
export SYNAPSOR_RUNNER_HTTP_TOKEN="$(node -e 'process.stdout.write(require("node:crypto").randomBytes(32).toString("base64url"))')"
|
|
524
524
|
|
|
525
525
|
npx -y -p @synapsor/runner synapsor-runner up --serve \
|
|
526
526
|
--config ./synapsor.runner.json \
|
|
@@ -543,9 +543,12 @@ npx -y -p @synapsor/runner synapsor-runner mcp serve-streamable-http \
|
|
|
543
543
|
```
|
|
544
544
|
|
|
545
545
|
Streamable HTTP defaults to `127.0.0.1:8766` and requires bearer auth by
|
|
546
|
-
default.
|
|
546
|
+
default. A non-loopback listener refuses to start until direct TLS, an explicitly
|
|
547
|
+
trusted TLS proxy, or authenticated emergency break glass is selected. Shared
|
|
548
|
+
services use signed per-session identity rather than one endpoint token. See
|
|
547
549
|
[HTTP MCP](http-mcp.md). If you want the smaller JSON-RPC bridge instead, use
|
|
548
|
-
`synapsor-runner mcp serve-http
|
|
550
|
+
`synapsor-runner mcp serve-http`; it is not suitable for claim-bound shared MCP
|
|
551
|
+
sessions.
|
|
549
552
|
|
|
550
553
|
The model-facing MCP server exposes semantic tools such as:
|
|
551
554
|
|
|
@@ -27,13 +27,22 @@ does not fit either direct boundary.
|
|
|
27
27
|
|
|
28
28
|
| Operation | Required source guard | Direct-write behavior |
|
|
29
29
|
| --- | --- | --- |
|
|
30
|
-
| `UPDATE` | Primary key, trusted tenant, exact version/conflict column | Patches only allowlisted columns and affects exactly one row. In Runner-ledger mode, the version must advance in the same source transaction. |
|
|
30
|
+
| `UPDATE` | Primary key, trusted tenant, and normally an exact version/conflict column | Patches only allowlisted columns and affects exactly one row. In Runner-ledger mode, the exact version must advance in the same source transaction. |
|
|
31
31
|
| `INSERT` | Source `PRIMARY KEY` or `UNIQUE` constraint over the reviewed dedup identity | Runner injects tenant and proposal-derived identity values, inserts only allowlisted fields, and requires exactly one row. |
|
|
32
32
|
| `DELETE` | Primary key, trusted tenant, exact version column | Deletes exactly one reviewed row. Hard delete requires human/operator approval and is refused when Runner detects write triggers or widening cascades. Prefer soft delete as guarded `UPDATE`. |
|
|
33
33
|
|
|
34
34
|
Existing proposal capabilities with no `operation` field continue to mean
|
|
35
35
|
single-row `UPDATE`.
|
|
36
36
|
|
|
37
|
+
DSL authoring requires `CONFLICT GUARD <column>` for UPDATE and never silently
|
|
38
|
+
chooses weak semantics. A narrow legacy single-row, non-reversible UPDATE using
|
|
39
|
+
source-DB receipt authority may explicitly declare
|
|
40
|
+
`CONFLICT GUARD WEAK ROW HASH ACKNOWLEDGED`. That mode hashes only the captured
|
|
41
|
+
projection and may miss concurrent changes outside it. It is prominently
|
|
42
|
+
reported by lint/explain/doctor/preview and is unavailable for Runner-ledger,
|
|
43
|
+
DELETE, reversibility, or bounded sets. Treat it as a compatibility escape
|
|
44
|
+
hatch, not equivalent protection.
|
|
45
|
+
|
|
37
46
|
## Receipt authority
|
|
38
47
|
|
|
39
48
|
Receipt authority and source-table provisioning are separate decisions:
|