@synapsor/runner 1.5.3 → 1.6.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.
Files changed (69) hide show
  1. package/CHANGELOG.md +82 -2
  2. package/README.md +131 -130
  3. package/dist/authoring.mjs +405 -9
  4. package/dist/cli.d.ts.map +1 -1
  5. package/dist/local-ui.d.ts +2 -0
  6. package/dist/local-ui.d.ts.map +1 -1
  7. package/dist/runner.mjs +19532 -14246
  8. package/dist/runtime.mjs +10856 -8495
  9. package/dist/shadow.mjs +101 -1
  10. package/docs/README.md +14 -6
  11. package/docs/aggregate-reads.md +22 -0
  12. package/docs/auto-boundary-and-scoped-explore.md +338 -0
  13. package/docs/capability-authoring.md +36 -1
  14. package/docs/cloud-mode.md +18 -0
  15. package/docs/conformance.md +16 -0
  16. package/docs/current-scope.md +55 -28
  17. package/docs/cursor-plugin.md +20 -3
  18. package/docs/database-enforced-scope.md +8 -0
  19. package/docs/dsl-reference.md +123 -4
  20. package/docs/getting-started-own-database.md +42 -35
  21. package/docs/guarded-crud-writeback.md +10 -1
  22. package/docs/http-mcp.md +222 -207
  23. package/docs/limitations.md +33 -9
  24. package/docs/local-mode.md +5 -2
  25. package/docs/mcp-client-setup.md +24 -11
  26. package/docs/mcp-clients.md +10 -4
  27. package/docs/openai-agents-sdk.md +16 -2
  28. package/docs/production.md +72 -7
  29. package/docs/release-notes.md +77 -3
  30. package/docs/runner-bundles.md +7 -2
  31. package/docs/runner-config-reference.md +96 -6
  32. package/docs/running-a-runner-fleet.md +42 -13
  33. package/docs/schema-api-candidates.md +28 -1
  34. package/docs/security-boundary.md +38 -3
  35. package/docs/store-lifecycle.md +93 -5
  36. package/docs/troubleshooting-first-run.md +98 -0
  37. package/examples/auto-boundary-churn/README.md +23 -0
  38. package/examples/auto-boundary-churn/app/page.tsx +8 -0
  39. package/examples/auto-boundary-churn/docker-compose.yml +16 -0
  40. package/examples/auto-boundary-churn/package.json +18 -0
  41. package/examples/auto-boundary-churn/prisma/schema.prisma +36 -0
  42. package/examples/auto-boundary-churn/seed/postgres.sql +126 -0
  43. package/examples/openai-agents-http/README.md +10 -4
  44. package/examples/openai-agents-http/agent.py +2 -2
  45. package/examples/runner-fleet/Dockerfile +7 -2
  46. package/examples/runner-fleet/README.md +11 -7
  47. package/examples/runner-fleet/docker-compose.yml +4 -4
  48. package/examples/runner-fleet/mint-dev-token.mjs +1 -1
  49. package/examples/runner-fleet/start-tls-runner.sh +21 -0
  50. package/examples/runner-fleet/synapsor.runner.json +27 -1
  51. package/examples/support-plan-credit/README.md +6 -1
  52. package/examples/support-plan-credit/mcp-client-examples/generic-streamable-http.json +4 -1
  53. package/examples/support-plan-credit/mcp-client-examples/openai-agents-streamable-http.ts +4 -0
  54. package/examples/support-plan-credit/synapsor.contract.json +0 -3
  55. package/fixtures/compatibility/published-1.5.4/manifest.json +76 -0
  56. package/fixtures/compatibility/published-1.5.4/sources/packages/dsl/examples/aggregate-read.synapsor.sql +21 -0
  57. package/fixtures/compatibility/published-1.5.4/sources/packages/dsl/examples/billing-late-fee.synapsor.sql +56 -0
  58. package/fixtures/compatibility/published-1.5.4/sources/packages/dsl/examples/bounded-set-multi-term.synapsor.sql +30 -0
  59. package/fixtures/compatibility/published-1.5.4/sources/packages/dsl/examples/principal-row-scope.synapsor.sql +23 -0
  60. package/fixtures/compatibility/published-1.5.4/sources/packages/spec/fixtures/conformance/aggregate-read/contract.json +119 -0
  61. package/fixtures/compatibility/published-1.5.4/sources/packages/spec/fixtures/conformance/approval-quorum/contract.json +44 -0
  62. package/fixtures/compatibility/published-1.5.4/sources/packages/spec/fixtures/conformance/bounded-set-threats/contract.json +115 -0
  63. package/fixtures/compatibility/published-1.5.4/sources/packages/spec/fixtures/conformance/principal-row-scope/contract.json +78 -0
  64. package/fixtures/compatibility/published-1.5.4/sources/packages/spec/fixtures/conformance/proposal-capability/contract.json +101 -0
  65. package/fixtures/compatibility/published-1.5.4/sources/packages/spec/fixtures/conformance/reversible-change-sets/contract.json +98 -0
  66. package/fixtures/compatibility/published-1.5.4/sources/packages/spec/fixtures/valid/basic-read.contract.json +60 -0
  67. package/llms.txt +37 -0
  68. package/package.json +7 -4
  69. package/schemas/synapsor.runner.schema.json +98 -1
@@ -49,10 +49,13 @@ developer path. Streamable HTTP implements MCP initialize/session behavior over
49
49
  an authenticated `/mcp` endpoint. The JSON-RPC bridge is intentionally smaller
50
50
  and does not implement MCP initialize/session behavior.
51
51
 
52
- HTTP requires bearer auth by default:
52
+ HTTP requires Bearer authentication by default. `Bearer` is the HTTP
53
+ presentation scheme; for this loopback example it carries one opaque random
54
+ endpoint token, not a user identity or JWT. The operator generates it and
55
+ provisions the same environment value to Runner and the authorized client:
53
56
 
54
57
  ```bash
55
- export SYNAPSOR_RUNNER_HTTP_TOKEN="dev-local-token"
58
+ export SYNAPSOR_RUNNER_HTTP_TOKEN="$(node -e 'process.stdout.write(require("node:crypto").randomBytes(32).toString("base64url"))')"
56
59
 
57
60
  npx -y -p @synapsor/runner synapsor-runner mcp serve-streamable-http \
58
61
  --config ./synapsor.runner.json \
@@ -88,8 +91,10 @@ npx -y -p @synapsor/runner synapsor-runner tools preview \
88
91
  --alias-mode openai
89
92
  ```
90
93
 
91
- Use private networking/TLS before exposing HTTP MCP beyond localhost. Details:
92
- [HTTP MCP](http-mcp.md).
94
+ Do not distribute one opaque token to unrelated users or tenants. A
95
+ non-loopback service also needs direct TLS or an explicitly trusted TLS proxy;
96
+ a shared service uses short-lived signed JWTs issued by your identity provider,
97
+ with tenant/principal bound from verified claims. Details: [HTTP MCP](http-mcp.md).
93
98
 
94
99
  ## Generate A Client Snippet
95
100
 
@@ -142,8 +147,10 @@ npx -y -p @synapsor/runner synapsor-runner mcp configure \
142
147
  When the destination already exists, Synapsor Runner creates a timestamped
143
148
  backup before writing. Noninteractive scripts must add `--yes`.
144
149
 
145
- The command writes only the local stdio MCP command and args. It does not write
146
- database URLs or passwords into the client config.
150
+ The command writes only command arguments and, for HTTP clients, credential
151
+ environment references plus external authorization metadata. It never writes
152
+ database URLs, passwords, token values, client secrets, or refresh tokens into
153
+ the generated client config.
147
154
 
148
155
  No separate Apps flag is required. A compatible host discovers
149
156
  `ui://synapsor/proposal-review.html` from proposal-tool metadata. The app can
@@ -165,8 +172,6 @@ the current `latest` release.
165
172
  For standard app/server HTTP MCP mode:
166
173
 
167
174
  ```bash
168
- export SYNAPSOR_RUNNER_HTTP_TOKEN="dev-local-token"
169
-
170
175
  npx -y -p @synapsor/runner synapsor-runner mcp serve-streamable-http \
171
176
  --config ./examples/mcp-postgres-billing/synapsor.runner.json \
172
177
  --store ./.synapsor/local.db \
@@ -174,6 +179,8 @@ npx -y -p @synapsor/runner synapsor-runner mcp serve-streamable-http \
174
179
  ```
175
180
 
176
181
  For the smaller JSON-RPC bridge, use `synapsor-runner mcp serve-http` instead.
182
+ The bridge is not standard Streamable HTTP and cannot provide claim-bound MCP
183
+ sessions, so do not use it for a shared identity deployment.
177
184
 
178
185
  ## Generic stdio Client
179
186
 
@@ -195,9 +202,9 @@ For the smaller JSON-RPC bridge, use `synapsor-runner mcp serve-http` instead.
195
202
  "./.synapsor/local.db"
196
203
  ],
197
204
  "env": {
198
- "BILLING_POSTGRES_READ_URL": "postgresql://synapsor_reader:...@localhost:55433/synapsor_runner_mcp_billing",
199
- "SYNAPSOR_TENANT_ID": "acme",
200
- "SYNAPSOR_PRINCIPAL": "local_billing_agent"
205
+ "BILLING_POSTGRES_READ_URL": "${BILLING_POSTGRES_READ_URL}",
206
+ "SYNAPSOR_TENANT_ID": "${SYNAPSOR_TENANT_ID}",
207
+ "SYNAPSOR_PRINCIPAL": "${SYNAPSOR_PRINCIPAL}"
201
208
  }
202
209
  }
203
210
  }
@@ -265,6 +272,12 @@ examples/mcp-client-configs/vscode.json
265
272
 
266
273
  Each example uses the same stdio command/args/env structure. Replace the placeholder environment variables in your client settings or shell environment.
267
274
 
275
+ These checked-in desktop examples use stdio, which is the recommended local
276
+ path for Claude Desktop, Cursor, and VS Code. The OpenAI Agents SDK recipe is
277
+ syntax-checked, and the HTTP endpoint is interoperability-tested with the
278
+ official MCP TypeScript client; repository CI does not claim to exercise an
279
+ identity-provider login inside every third-party host.
280
+
268
281
  Do not add a write database URL to the MCP server environment unless you are intentionally running a local review/writeback demo. For normal read/proposal tool calls, use the read URL and trusted context values only.
269
282
 
270
283
  Before documenting a client UI as officially tested, verify:
@@ -115,19 +115,25 @@ metadata preserves the canonical dotted Synapsor capability name.
115
115
  ## Streamable HTTP
116
116
 
117
117
  ```bash
118
+ export SYNAPSOR_RUNNER_HTTP_TOKEN="$(node -e 'process.stdout.write(require("node:crypto").randomBytes(32).toString("base64url"))')"
119
+
118
120
  synapsor-runner mcp serve \
119
121
  --transport streamable-http \
120
122
  --alias-mode openai \
121
123
  --host 127.0.0.1 \
122
124
  --port 8766 \
123
125
  --config ./synapsor.runner.json \
124
- --store ./.synapsor/local.db
126
+ --store ./.synapsor/local.db \
127
+ --auth-token-env SYNAPSOR_RUNNER_HTTP_TOKEN
125
128
  ```
126
129
 
127
130
  Connect a standard Streamable HTTP MCP client to
128
- `http://127.0.0.1:8766/mcp`. Keep it on loopback for local development. For a
129
- network deployment, terminate TLS, require authentication, restrict network
130
- access, and follow the [production guide](production.md).
131
+ `http://127.0.0.1:8766/mcp` with `Authorization: Bearer` loaded from the same
132
+ protected environment value. Keep it on loopback for local development. A
133
+ non-loopback listener refuses to bind without direct TLS, an explicitly trusted
134
+ TLS proxy, or authenticated emergency break glass. Shared deployments require
135
+ identity-provider-issued signed claims. Follow [HTTP MCP](http-mcp.md) and the
136
+ [production guide](production.md).
131
137
 
132
138
  Use `--alias-mode openai` for the OpenAI Agents SDK. Omit it for clients that
133
139
  accept canonical dotted names, or use `--alias-mode both` only during a planned
@@ -8,7 +8,7 @@ surface.
8
8
  ## Start Runner
9
9
 
10
10
  ```bash
11
- export SYNAPSOR_RUNNER_HTTP_TOKEN="dev-local-token"
11
+ export SYNAPSOR_RUNNER_HTTP_TOKEN="$(node -e 'process.stdout.write(require("node:crypto").randomBytes(32).toString("base64url"))')"
12
12
 
13
13
  npx -y -p @synapsor/runner synapsor-runner mcp serve-streamable-http \
14
14
  --config ./synapsor.runner.json \
@@ -17,6 +17,11 @@ npx -y -p @synapsor/runner synapsor-runner mcp serve-streamable-http \
17
17
  --alias-mode openai
18
18
  ```
19
19
 
20
+ For this loopback/single-service example, the operator provisions that same
21
+ opaque value to the Runner process and the Python client through protected
22
+ environment injection. HTTP `Bearer` describes how the credential is sent; it
23
+ does not make the opaque value a JWT, and Runner does not issue it.
24
+
20
25
  `--alias-mode openai` is important because OpenAI function/tool names cannot
21
26
  contain dots. Runner exposes names such as `billing__inspect_invoice` to the
22
27
  model and maps calls back to canonical Synapsor capability names such as
@@ -28,7 +33,8 @@ model and maps calls back to canonical Synapsor capability names such as
28
33
  npx -y -p @synapsor/runner synapsor-runner mcp client-config \
29
34
  --client openai-agents \
30
35
  --config ./synapsor.runner.json \
31
- --store ./.synapsor/local.db
36
+ --store ./.synapsor/local.db \
37
+ --client-access-token-env SYNAPSOR_RUNNER_HTTP_TOKEN
32
38
  ```
33
39
 
34
40
  The generated output includes:
@@ -41,6 +47,14 @@ The generated output includes:
41
47
  It does not include database URLs, passwords, write credentials, API keys, or
42
48
  bearer token values.
43
49
 
50
+ For a shared deployment, configure `http_claims`, asymmetric `session_auth`, and
51
+ `http_security.oauth_resource`, then use a separate env name such as
52
+ `SYNAPSOR_MCP_ACCESS_TOKEN`. Your configured identity provider issues that
53
+ short-lived token for the protected-resource audience; Runner validates it on
54
+ every request but never stores end-user passwords or issues/refreshes tokens.
55
+ The generated client URL is the configured HTTPS protected resource. See
56
+ [HTTP MCP](http-mcp.md) for the complete profile and TLS/proxy requirements.
57
+
44
58
  ## Sanity Check
45
59
 
46
60
  Before giving the agent a real task, ask it to inspect one known object and
@@ -421,6 +421,36 @@ synapsor-runner activity search --proposal wrp_... --store ./.synapsor/local.db
421
421
 
422
422
  ## Deployment Recipes
423
423
 
424
+ ### Networked MCP boundary
425
+
426
+ Prefer stdio when one local client can launch Runner. For Streamable HTTP,
427
+ choose the deployment profile before binding:
428
+
429
+ - loopback: authenticated opaque endpoint token by default;
430
+ - remote single-tenant: direct TLS or an explicitly trusted TLS proxy, plus one
431
+ high-entropy rotated service token;
432
+ - shared multi-user/tenant: direct TLS or trusted proxy, asymmetric signed JWT
433
+ sessions, exact issuer/audience/resource, verified tenant/principal claims,
434
+ and RFC 9728 protected-resource metadata.
435
+
436
+ An opaque token is generated by the operator and provisioned to server and
437
+ authorized client through environment/secret-manager injection. Runner does not
438
+ issue it. In a shared deployment, the configured external identity provider
439
+ issues short-lived access tokens; Runner verifies them but does not store user
440
+ passwords or issue refresh tokens. See [HTTP MCP](http-mcp.md) before exposing a
441
+ listener outside loopback.
442
+
443
+ Run the transport-specific doctor check before serving:
444
+
445
+ ```bash
446
+ synapsor-runner doctor \
447
+ --config ./synapsor.runner.json \
448
+ --transport streamable-http \
449
+ --host 0.0.0.0 \
450
+ --trusted-tls-proxy \
451
+ --auth-token-env SYNAPSOR_RUNNER_HTTP_TOKEN
452
+ ```
453
+
424
454
  ### Docker Compose Shape
425
455
 
426
456
  Use Compose to run the MCP server next to your app and database network. Keep
@@ -436,14 +466,20 @@ export SYNAPSOR_SECRET_MAP='{
436
466
  }'
437
467
 
438
468
  synapsor-runner mcp serve-streamable-http \
469
+ --host 0.0.0.0 \
439
470
  --config ./synapsor.runner.json \
440
471
  --store ./.synapsor/local.db \
472
+ --trusted-tls-proxy \
473
+ --auth-token-env SYNAPSOR_RUNNER_HTTP_TOKEN \
441
474
  --secrets-provider aws-secretsmanager-cli \
442
475
  --secret-map-env SYNAPSOR_SECRET_MAP
443
476
  ```
444
477
 
445
478
  The AWS provider shells out to `aws secretsmanager get-secret-value`; the runner
446
- logs only how many target env vars were loaded or skipped.
479
+ logs only how many target env vars were loaded or skipped. This command assumes
480
+ `http_security.deployment: single_tenant`, an exact `allowed_hosts` list, and a
481
+ TLS proxy on a private hop. Use asymmetric claim-bound identity instead for a
482
+ shared user/tenant service.
447
483
 
448
484
  ```yaml
449
485
  services:
@@ -453,22 +489,30 @@ services:
453
489
  command: >
454
490
  sh -lc "npm install -g @synapsor/runner &&
455
491
  synapsor-runner up --review --config /config/synapsor.runner.json
456
- --store /data/local.db --host 0.0.0.0 --port 8766"
492
+ --store /data/local.db --host 0.0.0.0 --port 8766
493
+ --trusted-tls-proxy --auth-token-env SYNAPSOR_RUNNER_HTTP_TOKEN"
457
494
  environment:
458
495
  SYNAPSOR_DATABASE_READ_URL: ${SYNAPSOR_DATABASE_READ_URL}
459
496
  SYNAPSOR_DATABASE_WRITE_URL: ${SYNAPSOR_DATABASE_WRITE_URL}
460
497
  SYNAPSOR_TENANT_ID: ${SYNAPSOR_TENANT_ID}
461
498
  SYNAPSOR_PRINCIPAL: ${SYNAPSOR_PRINCIPAL}
499
+ SYNAPSOR_RUNNER_HTTP_TOKEN: ${SYNAPSOR_RUNNER_HTTP_TOKEN}
462
500
  volumes:
463
501
  - ./synapsor.runner.json:/config/synapsor.runner.json:ro
464
502
  - synapsor-runner-data:/data
465
- ports:
466
- - "8766:8766"
503
+ expose:
504
+ - "8766"
467
505
 
468
506
  volumes:
469
507
  synapsor-runner-data:
470
508
  ```
471
509
 
510
+ This shape deliberately does not publish the Runner port to the host. Put a
511
+ TLS-terminating proxy on the same private container network, publish only that
512
+ proxy, preserve the reviewed external Host, and prevent direct client access to
513
+ Runner. If Runner itself owns TLS, replace `--trusted-tls-proxy` with protected
514
+ `--tls-cert-env`/`--tls-key-env` PEM references.
515
+
472
516
  ### systemd Shape
473
517
 
474
518
  ```ini
@@ -491,10 +535,20 @@ PrivateTmp=true
491
535
  WantedBy=multi-user.target
492
536
  ```
493
537
 
494
- Put database credentials in `/etc/synapsor-runner.env` or your system secret
495
- manager, not in `synapsor.runner.json`.
538
+ Put database credentials and the random loopback endpoint token in
539
+ `/etc/synapsor-runner.env` or your system secret manager, not in
540
+ `synapsor.runner.json`. The systemd command binds loopback, so a local trusted
541
+ proxy may connect to it without exposing cleartext Bearer traffic on the
542
+ network.
496
543
 
497
- ## TLS And SSL
544
+ ## TLS, mTLS, And Database SSL
545
+
546
+ For MCP HTTP, Runner-owned TLS reads certificate/key PEM only from named
547
+ environment variables. Optional mTLS verifies client certificates against an
548
+ explicit CA and supplements Bearer authentication; it does not replace
549
+ per-session tenant/principal identity in a shared service. A trusted proxy must
550
+ be protected from direct bypass and must not inject forwarded tenant/principal
551
+ headers as authority.
498
552
 
499
553
  For staging or production-like databases:
500
554
 
@@ -507,6 +561,17 @@ For staging or production-like databases:
507
561
  For disposable local Docker fixtures, `sslmode=disable` is acceptable when the
508
562
  database is bound to loopback and contains only test data.
509
563
 
564
+ Rotate credentials independently:
565
+
566
+ - opaque endpoint token: active plus at most one previous env during rollout;
567
+ - HS256 session key: active plus one previous key, only for controlled use;
568
+ - asymmetric JWT key: publish a new `kid`, honor bounded JWKS cache/cooldown,
569
+ then retire the old public key after clients rotate;
570
+ - TLS certificate and mTLS client CA: roll protected PEM material and restart
571
+ instances without changing tenant identity;
572
+ - database and handler credentials: rotate their separate env/secret-manager
573
+ entries without reusing an MCP or Cloud credential.
574
+
510
575
  ## Health And Doctor
511
576
 
512
577
  Run these before exposing tools to an agent:
@@ -10,7 +10,81 @@ 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.5.3 (prepared, not published)
13
+ ## 1.6.0 (prepared, not published)
14
+
15
+ ### Connect, Explore, Protect
16
+
17
+ - A fresh interactive `start --from-env DATABASE_URL` can inspect the whole
18
+ staging schema and structured Prisma/Drizzle/OpenAPI/Synapsor artifacts,
19
+ then emit a disabled candidate boundary without executing adopter code,
20
+ reading source rows, or using an LLM.
21
+ - The local Workbench requires explicit review of scope, fields, aggregate
22
+ measures/dimensions/time buckets, one-hop relationships, privacy budgets,
23
+ role/grant/RLS posture, profile, generation lock, and exact digest.
24
+ - The temporary Cursor authoring entry exposes only `app.describe_data` and
25
+ `app.explore_data`. Typed row and PM-style aggregate plans are bounded by the
26
+ activated authority, run read-only, and cannot introduce SQL, identifiers,
27
+ tenant/principal identity, or wider limits.
28
+ - Aggregate Explore supports reviewed count/distinct/sum/avg, categorical and
29
+ time grouping, bounded comparisons/top-N, cohort suppression, and durable
30
+ anti-differencing/extraction/rate limits. It is descriptive analysis, not a
31
+ causation claim.
32
+ - Protect This Query writes public DSL, canonical JSON, tests, and a disabled
33
+ named capability. Exact-digest activation disables broad Explore; production
34
+ serves only the named protected tool.
35
+ - Scoped Explore is absent from production, unknown-profile, shared HTTP,
36
+ remote, and non-loopback `tools/list`. Write-capable, owner, superuser,
37
+ `BYPASSRLS`, or unverifiable credentials cannot enable source-row Explore.
38
+ - Existing 1.x projects do not need Workbench, generation locks, rescans, or
39
+ new fields. Published legacy contracts preserve exact normalization/digests,
40
+ and established CLI/headless/CI routes keep their behavior.
41
+
42
+ Prepared package versions: `@synapsor/runner@1.6.0`,
43
+ `@synapsor/dsl@1.5.0`, and `@synapsor/spec@1.5.0`. Nothing has been published
44
+ by this repository change.
45
+
46
+ ## 1.5.4 (published 2026-07-22)
47
+
48
+ ### Networked MCP authentication hardening
49
+
50
+ - Local stdio still opens no network listener and needs no HTTP credential.
51
+ Loopback HTTP remains authenticated by default with an operator-provisioned
52
+ opaque token.
53
+ - Remote HTTP now refuses to bind over an undeclared cleartext channel. Operators
54
+ must use Runner-owned TLS, explicitly declare a trusted TLS proxy, or select an
55
+ authenticated and prominently diagnosed break-glass posture.
56
+ - Opaque endpoint tokens are constrained to local or explicit single-tenant use.
57
+ Remote use requires adequate entropy; one active and one previous env-provided
58
+ value support bounded rotation without logging either value.
59
+ - Shared deployments require signed per-session identity and `http_claims`
60
+ trusted context. Runner revalidates issuer, audience/resource, time, scope,
61
+ tenant, principal, signature, and algorithm on every request and rejects token
62
+ or identity swaps inside an MCP session.
63
+ - RFC 9728 protected-resource metadata and Bearer challenges let compatible MCP
64
+ clients discover the configured external authorization server. Runner verifies
65
+ access tokens but does not implement user login or token issuance.
66
+ - TLS/mTLS preflight, public-only bounded JWKS handling, exact Origin/Host checks,
67
+ request/session bounds, doctor diagnostics, client recipes, and fleet examples
68
+ now share one documented deployment ladder.
69
+ - `lifecycle`, `lifecycle show`, and `lifecycle show latest` now inspect the
70
+ newest complete proposal lifecycle without an id. Filters and known
71
+ proposal/evidence/replay/job/intent/receipt/audit handles resolve a stable,
72
+ read-only `synapsor.lifecycle-view.v1` domain document across local SQLite
73
+ and shared PostgreSQL runtime stores.
74
+ - DSL UPDATE authoring now requires an exact `CONFLICT GUARD <column>` instead
75
+ of silently choosing projection hashing. A reviewer-visible weak compatibility
76
+ clause remains only for ordinary single-row source-DB UPDATE and is rejected
77
+ for the stronger operation modes.
78
+ - Runner rejects canonical `FROM SESSION` with
79
+ `SESSION_BINDING_UNSUPPORTED` rather than treating its key as a process
80
+ environment variable. Explicit ENVIRONMENT, verified HTTP_CLAIM, verified
81
+ CLOUD_SESSION, and STATIC_DEV behavior remains distinct.
82
+
83
+ Published package versions: `@synapsor/runner@1.5.4` and
84
+ `@synapsor/dsl@1.4.4`. `@synapsor/spec@1.4.2` and the Cloud CLI were
85
+ unchanged.
86
+
87
+ ## 1.5.3 (published 2026-07-21)
14
88
 
15
89
  ### Intent to Safe Action
16
90
 
@@ -37,8 +111,8 @@ for the Synapsor Cloud CLI.
37
111
  - The first two README screens, public website, article, discovery routes, and
38
112
  36-second evidence-backed demo now lead with the own-project Data PR path.
39
113
 
40
- Prepared package version: `@synapsor/runner@1.5.3`. Spec, DSL, and Cloud CLI
41
- packages are unchanged. Nothing has been published by this repository change.
114
+ Published package version: `@synapsor/runner@1.5.3`. Spec, DSL, and Cloud CLI
115
+ packages were unchanged.
42
116
 
43
117
  ## 1.5.2 (prepared, not published)
44
118
 
@@ -67,8 +67,13 @@ npx -y -p @synapsor/runner synapsor-runner cloud sync latest \
67
67
 
68
68
  For the OpenAI Agents SDK, use the included TypeScript examples. Their stdio
69
69
  command enables `--alias-mode openai`; the Streamable HTTP example includes the
70
- matching server command. Claude, Cursor, and generic templates use canonical
71
- dotted capability names.
70
+ matching server command and reads its Bearer credential from
71
+ `SYNAPSOR_RUNNER_HTTP_TOKEN`. The generic HTTP template uses the same environment
72
+ reference. Neither file embeds a credential value. An operator generates and
73
+ provisions the opaque token to the Runner process and authorized client; use a
74
+ signed identity-provider token instead for shared multi-user deployments. See
75
+ [HTTP MCP](http-mcp.md). Claude, Cursor, and generic stdio templates use
76
+ canonical dotted capability names and need no HTTP credential.
72
77
 
73
78
  Approval and writeback remain outside the model-facing MCP tool surface. Use
74
79
  `proposals`, `apply`, `receipts`, and `replay` from a trusted operator shell.
@@ -36,6 +36,7 @@ Unknown keys fail when `strict` is true (the default).
36
36
  | `approvals` | No | Local approval overrides. |
37
37
  | `operator_identity` | No | Verified operator identity and apply-role wiring for approve/reject/apply. |
38
38
  | `session_auth` | HTTP claims | HS256 development or asymmetric RS256/ES256 session-token verification. |
39
+ | `http_security` | Networked HTTP | Deployment profile, protected channel, endpoint-token env names, OAuth protected-resource metadata, exact Origin/Host policy, and request/session bounds. |
39
40
  | `rate_limits` | No | Operational fixed-window limits; fleet-wide only with shared `runtime_store`. |
40
41
  | `metrics` | No | Separately authorized scrapeable HTTP metrics. Disabled by default. |
41
42
  | `graduated_trust` | No | Off-by-default, operator-only policy recommendation criteria and kill switch. |
@@ -212,8 +213,16 @@ Providers are `environment`, `static_dev`, `http_claims`, and `cloud_session`.
212
213
  Capabilities may reference a context by name. The model never receives tenant
213
214
  or principal as an overridable argument.
214
215
 
215
- For multi-tenant Streamable HTTP services, use `http_claims` plus signed
216
- session auth:
216
+ The language-neutral canonical contract can preserve a `session` binding for
217
+ implementations such as C++/Cloud that own a typed session boundary. Runner has
218
+ no generic web-session provider and rejects it with
219
+ `SESSION_BINDING_UNSUPPORTED`; it never treats the key as an environment
220
+ variable. Use `environment` for local stdio, verified `http_claims` for shared
221
+ Streamable HTTP, or verified `cloud_session` for Cloud embedding.
222
+
223
+ For controlled loopback development, `http_claims` can use an HS256 session
224
+ token. Runner holds the symmetric signing/verification secret in this mode, so
225
+ it is not the recommended shared production profile:
217
226
 
218
227
  ```json
219
228
  {
@@ -229,7 +238,7 @@ session auth:
229
238
  "secret_env": "SYNAPSOR_SESSION_JWT_SECRET",
230
239
  "previous_secret_env": "SYNAPSOR_PREVIOUS_SESSION_JWT_SECRET",
231
240
  "issuer": "https://identity.example",
232
- "audience": "synapsor-runner"
241
+ "audience": "https://runner.example/mcp"
233
242
  }
234
243
  }
235
244
  ```
@@ -239,16 +248,24 @@ the active secret first, then the previous secret. Existing MCP sessions remain
239
248
  bound to the exact token fingerprint, so clients cannot swap tenant/principal
240
249
  identity inside an initialized session.
241
250
 
242
- For networked deployments prefer asymmetric public-key verification:
251
+ For a shared networked deployment, use asymmetric public-key verification and
252
+ declare the protected resource and channel explicitly:
243
253
 
244
254
  ```json
245
255
  {
256
+ "trusted_context": {
257
+ "provider": "http_claims",
258
+ "values": {
259
+ "tenant_id_key": "tenant_id",
260
+ "principal_key": "sub"
261
+ }
262
+ },
246
263
  "session_auth": {
247
264
  "provider": "jwt_asymmetric",
248
- "algorithms": ["RS256"],
265
+ "algorithms": ["RS256", "ES256"],
249
266
  "jwks_url_env": "SYNAPSOR_SESSION_JWKS_URL",
250
267
  "issuer": "https://identity.example",
251
- "audience": "synapsor-runner",
268
+ "audience": "https://runner.example/mcp",
252
269
  "tenant_claim": "tenant_id",
253
270
  "principal_claim": "sub",
254
271
  "clock_skew_seconds": 30,
@@ -256,6 +273,19 @@ For networked deployments prefer asymmetric public-key verification:
256
273
  "jwks_cooldown_seconds": 30,
257
274
  "fetch_timeout_ms": 3000,
258
275
  "max_response_bytes": 1048576
276
+ },
277
+ "http_security": {
278
+ "deployment": "shared",
279
+ "channel": "trusted_tls_proxy",
280
+ "oauth_resource": {
281
+ "resource": "https://runner.example/mcp",
282
+ "authorization_servers": ["https://identity.example"],
283
+ "scopes_supported": ["synapsor:mcp"],
284
+ "required_scopes": ["synapsor:mcp"],
285
+ "resource_name": "Synapsor Runner"
286
+ },
287
+ "allowed_origins": ["https://agent-console.example"],
288
+ "allowed_hosts": ["runner.example"]
259
289
  }
260
290
  }
261
291
  ```
@@ -268,6 +298,66 @@ rejected. Every effective named context in an `http_claims` server must bind
268
298
  tenant and principal from claims; environment/static contradictions fail with
269
299
  `TRUSTED_CONTEXT_PROVIDER_CONFLICT` before serving.
270
300
 
301
+ `session_auth` verifies identity; it does not issue access tokens. The external
302
+ identity provider identified by `issuer` issues a short-lived token whose
303
+ audience is the exact protected-resource URL. Runner fetches only public JWKS
304
+ material and validates the configured claims on every request. Required OAuth
305
+ scopes are declared under `http_security.oauth_resource`.
306
+
307
+ ## HTTP security
308
+
309
+ `http_security` is Runner deployment wiring, not part of a portable contract:
310
+
311
+ ```json
312
+ {
313
+ "http_security": {
314
+ "deployment": "single_tenant",
315
+ "channel": "trusted_tls_proxy",
316
+ "static_token": {
317
+ "active_env": "SYNAPSOR_RUNNER_HTTP_TOKEN",
318
+ "previous_env": "SYNAPSOR_RUNNER_HTTP_TOKEN_PREVIOUS"
319
+ },
320
+ "allowed_origins": ["https://agent-console.example"],
321
+ "allowed_hosts": ["runner.internal.example"],
322
+ "limits": {
323
+ "max_request_bytes": 65536,
324
+ "max_header_bytes": 8192,
325
+ "max_sessions": 500,
326
+ "session_idle_timeout_seconds": 300,
327
+ "request_timeout_ms": 15000,
328
+ "headers_timeout_ms": 5000,
329
+ "keep_alive_timeout_ms": 5000,
330
+ "max_connections": 1000
331
+ }
332
+ }
333
+ }
334
+ ```
335
+
336
+ - `deployment` is `loopback`, `single_tenant`, or `shared`. Shared requires
337
+ verified signed `http_claims`, exact issuer/audience, and RFC 9728
338
+ `oauth_resource` metadata.
339
+ - `channel` is `direct_tls`, `trusted_tls_proxy`, or the explicitly unsafe
340
+ `insecure_http_break_glass`. A non-loopback listener without one refuses to
341
+ bind. Break glass remains authenticated.
342
+ - `static_token` names one active and optionally one previous environment
343
+ variable. It is opaque service access for loopback/single-tenant use, not
344
+ tenant identity. Values never belong in config.
345
+ - `oauth_resource.resource` is the exact HTTPS MCP endpoint and must equal
346
+ `session_auth.audience`. `authorization_servers` names external issuers;
347
+ Runner does not implement login or token refresh.
348
+ - `allowed_origins` contains exact browser origins; an omitted browser Origin
349
+ remains valid for native clients. Wildcards are forbidden.
350
+ - `allowed_hosts` contains exact direct/public Host authorities. Forwarded Host
351
+ and forwarded identity headers are not trusted automatically.
352
+ - `limits` bounds bodies, headers, concurrent sessions/connections, idle
353
+ sessions, and HTTP timing. Defaults are documented in [HTTP MCP](http-mcp.md).
354
+
355
+ Runner-owned TLS certificate, key, and optional client-CA PEM values are passed
356
+ by environment references on the serve command (`--tls-cert-env`,
357
+ `--tls-key-env`, `--tls-ca-env`, `--require-client-cert`). Private TLS material
358
+ does not go in this JSON file. A trusted-proxy channel assumes firewall/private
359
+ network controls prevent clients from bypassing that proxy.
360
+
271
361
  ## Contracts and embedded capabilities
272
362
 
273
363
  ```json
@@ -1,6 +1,6 @@
1
1
  # Running A Small Runner Fleet
2
2
 
3
- Synapsor Runner 1.1 supports a bounded small-fleet deployment. It does not
3
+ Synapsor Runner supports a bounded small-fleet deployment. It does not
4
4
  turn the OSS package into Synapsor Cloud and it does not promise an unbounded,
5
5
  multi-region, or SLA-backed control plane.
6
6
 
@@ -32,7 +32,7 @@ model-facing approval/apply tools as blockers. Audit is a static review, not a
32
32
  security proof; follow it with `tools preview`, `doctor`, and the live fleet
33
33
  verification.
34
34
 
35
- ## What 1.1 Guarantees
35
+ ## What The Bounded Fleet Guarantees
36
36
 
37
37
  With `storage.shared_postgres.mode = "runtime_store"`:
38
38
 
@@ -116,7 +116,7 @@ Use asymmetric verification for a networked fleet:
116
116
  "algorithms": ["RS256"],
117
117
  "jwks_url_env": "SYNAPSOR_SESSION_JWKS_URL",
118
118
  "issuer": "https://identity.example",
119
- "audience": "synapsor-runner",
119
+ "audience": "https://runner.example/mcp",
120
120
  "tenant_claim": "tenant_id",
121
121
  "principal_claim": "sub",
122
122
  "clock_skew_seconds": 30,
@@ -124,10 +124,28 @@ Use asymmetric verification for a networked fleet:
124
124
  "jwks_cooldown_seconds": 30,
125
125
  "fetch_timeout_ms": 3000,
126
126
  "max_response_bytes": 1048576
127
+ },
128
+ "http_security": {
129
+ "deployment": "shared",
130
+ "channel": "trusted_tls_proxy",
131
+ "oauth_resource": {
132
+ "resource": "https://runner.example/mcp",
133
+ "authorization_servers": ["https://identity.example"],
134
+ "scopes_supported": ["synapsor:mcp"],
135
+ "required_scopes": ["synapsor:mcp"]
136
+ },
137
+ "allowed_hosts": ["runner.example"],
138
+ "allowed_origins": ["https://agent-console.example"]
127
139
  }
128
140
  }
129
141
  ```
130
142
 
143
+ The external identity provider issues the short-lived client JWT for resource
144
+ `https://runner.example/mcp`; Runner only verifies it. The load balancer must
145
+ terminate TLS and prevent direct access to the private Runner listeners. If
146
+ Runner owns TLS instead, use protected certificate/key env references and set
147
+ `channel` to `direct_tls`.
148
+
131
149
  Every served capability context must bind tenant and principal from
132
150
  `HTTP_CLAIM`. Runner rejects an environment-bound contract mixed into this
133
151
  server with `TRUSTED_CONTEXT_PROVIDER_CONFLICT` before tools are served.
@@ -147,6 +165,12 @@ material. Unknown `kid` triggers at most one controlled refresh. For offline
147
165
  deployments, configure one public PEM through `public_key_env` or
148
166
  `public_key_path`; never place a private key in Runner config.
149
167
 
168
+ Runner publishes RFC 9728 protected-resource metadata at the path derived from
169
+ the public resource and returns scope-aware Bearer challenges. The identity
170
+ provider, not Runner, owns user login, client registration, token issuance, and
171
+ refresh. An MCP session ID is only protocol state; every request is
172
+ reauthenticated and remains pinned to the initialized credential identity.
173
+
150
174
  ## Ledger And Source Pools
151
175
 
152
176
  ```json
@@ -329,12 +353,13 @@ configured handler timeout. A hard kill is recoverable through leases and
329
353
  source-side idempotency receipts, but should remain an incident signal.
330
354
 
331
355
  Rotate JWKS keys by publishing the new `kid`, waiting at least the configured
332
- cache lifetime, switching issuers, then retiring the old key. Rotate metrics,
333
- handler, database, and ledger credentials independently through their named
334
- environment variables.
356
+ cache lifetime while issuers sign with the new key, then retiring the old key.
357
+ Do not casually change the issuer or protected-resource audience during a key
358
+ rotation. Rotate TLS/mTLS, metrics, handler, database, and ledger credentials
359
+ independently through their named environment variables.
335
360
 
336
- Only homogeneous Runner 1.1 instances are verified by `test:fleet`. Mixed
337
- 1.0/1.1 operation is not claimed. For rolling upgrades:
361
+ Only homogeneous Runner instances at the release under test are verified by
362
+ `test:fleet`. Mixed-version operation is not claimed. For rolling upgrades:
338
363
 
339
364
  1. back up and verify the ledger;
340
365
  2. run schema/config validation with the target version;
@@ -351,8 +376,12 @@ roll back the full fleet. Never mix versions merely because both are `1.x`.
351
376
 
352
377
  ## TLS Boundary
353
378
 
354
- Terminate public TLS or mTLS at a trusted load balancer, or configure Runner's
355
- tested TLS/mTLS options directly. Keep Runner-to-database TLS verification on,
356
- use private networking where possible, restrict `/metrics`, and never forward
357
- untrusted identity headers as claims. The bearer JWT itself, not a proxy-added
358
- tenant header, is the session authority.
379
+ Terminate public TLS or mTLS at a trusted load balancer and declare
380
+ `channel: trusted_tls_proxy`, or configure Runner's tested TLS/mTLS options
381
+ directly. A proxy declaration is a security assertion: firewall the private
382
+ listener so clients cannot bypass the proxy, protect the private hop, and
383
+ preserve an allowed Host. Keep Runner-to-database TLS verification on, use
384
+ private networking where possible, and restrict `/metrics` separately. The
385
+ verified Bearer JWT itself, not a proxy-added tenant/principal header, is the
386
+ session authority. See [HTTP MCP](http-mcp.md) for channel flags, limits, and
387
+ doctor checks.