@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.
- package/CHANGELOG.md +82 -2
- package/README.md +131 -130
- package/dist/authoring.mjs +405 -9
- package/dist/cli.d.ts.map +1 -1
- package/dist/local-ui.d.ts +2 -0
- package/dist/local-ui.d.ts.map +1 -1
- package/dist/runner.mjs +19532 -14246
- package/dist/runtime.mjs +10856 -8495
- package/dist/shadow.mjs +101 -1
- package/docs/README.md +14 -6
- package/docs/aggregate-reads.md +22 -0
- package/docs/auto-boundary-and-scoped-explore.md +338 -0
- package/docs/capability-authoring.md +36 -1
- package/docs/cloud-mode.md +18 -0
- package/docs/conformance.md +16 -0
- package/docs/current-scope.md +55 -28
- package/docs/cursor-plugin.md +20 -3
- package/docs/database-enforced-scope.md +8 -0
- package/docs/dsl-reference.md +123 -4
- package/docs/getting-started-own-database.md +42 -35
- package/docs/guarded-crud-writeback.md +10 -1
- package/docs/http-mcp.md +222 -207
- package/docs/limitations.md +33 -9
- 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 +77 -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/schema-api-candidates.md +28 -1
- package/docs/security-boundary.md +38 -3
- package/docs/store-lifecycle.md +93 -5
- package/docs/troubleshooting-first-run.md +98 -0
- package/examples/auto-boundary-churn/README.md +23 -0
- package/examples/auto-boundary-churn/app/page.tsx +8 -0
- package/examples/auto-boundary-churn/docker-compose.yml +16 -0
- package/examples/auto-boundary-churn/package.json +18 -0
- package/examples/auto-boundary-churn/prisma/schema.prisma +36 -0
- package/examples/auto-boundary-churn/seed/postgres.sql +126 -0
- 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/fixtures/compatibility/published-1.5.4/manifest.json +76 -0
- package/fixtures/compatibility/published-1.5.4/sources/packages/dsl/examples/aggregate-read.synapsor.sql +21 -0
- package/fixtures/compatibility/published-1.5.4/sources/packages/dsl/examples/billing-late-fee.synapsor.sql +56 -0
- package/fixtures/compatibility/published-1.5.4/sources/packages/dsl/examples/bounded-set-multi-term.synapsor.sql +30 -0
- package/fixtures/compatibility/published-1.5.4/sources/packages/dsl/examples/principal-row-scope.synapsor.sql +23 -0
- package/fixtures/compatibility/published-1.5.4/sources/packages/spec/fixtures/conformance/aggregate-read/contract.json +119 -0
- package/fixtures/compatibility/published-1.5.4/sources/packages/spec/fixtures/conformance/approval-quorum/contract.json +44 -0
- package/fixtures/compatibility/published-1.5.4/sources/packages/spec/fixtures/conformance/bounded-set-threats/contract.json +115 -0
- package/fixtures/compatibility/published-1.5.4/sources/packages/spec/fixtures/conformance/principal-row-scope/contract.json +78 -0
- package/fixtures/compatibility/published-1.5.4/sources/packages/spec/fixtures/conformance/proposal-capability/contract.json +101 -0
- package/fixtures/compatibility/published-1.5.4/sources/packages/spec/fixtures/conformance/reversible-change-sets/contract.json +98 -0
- package/fixtures/compatibility/published-1.5.4/sources/packages/spec/fixtures/valid/basic-read.contract.json +60 -0
- package/llms.txt +37 -0
- package/package.json +7 -4
- package/schemas/synapsor.runner.schema.json +98 -1
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Reviewed Candidates From Prisma, Drizzle, And OpenAPI
|
|
2
2
|
|
|
3
|
+
Runner 1.6.0 uses these same bounded static parsers as evidence in the
|
|
4
|
+
whole-application [Auto Boundary](auto-boundary-and-scoped-explore.md) flow.
|
|
5
|
+
The database catalog remains authoritative for database structure; Prisma,
|
|
6
|
+
Drizzle, OpenAPI, and existing Synapsor definitions contribute deterministic
|
|
7
|
+
names, relationships, and structured review evidence. None of them
|
|
8
|
+
independently grant authority.
|
|
9
|
+
|
|
10
|
+
For a fresh interactive staging project, prefer:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
synapsor-runner start --from-env DATABASE_URL
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
That combines all detected structured sources into one disabled candidate
|
|
17
|
+
graph, generation lock, and Workbench review. It never executes adopter code,
|
|
18
|
+
samples source rows before activation, uses an LLM, or overwrites an active
|
|
19
|
+
contract.
|
|
20
|
+
|
|
21
|
+
The individual commands below remain useful for CI, focused review, and
|
|
22
|
+
backward-compatible workflows.
|
|
23
|
+
|
|
3
24
|
Use these generators after schema or API inspection when you want a review
|
|
4
25
|
starting point without granting authority:
|
|
5
26
|
|
|
@@ -42,11 +63,17 @@ They do not decide which tenant or principal is authoritative, which fields are
|
|
|
42
63
|
safe to expose, which writes are valid, business bounds, approval policy, or
|
|
43
64
|
auto-approval. A field-name heuristic is not data classification.
|
|
44
65
|
|
|
45
|
-
|
|
66
|
+
In the individual-generator path, review `generation-review.json` and
|
|
67
|
+
`REVIEW.md`, replace every
|
|
46
68
|
`review_required_*` placeholder, run the generated tests and a
|
|
47
69
|
[Shadow study](shadow-studies.md), then deliberately copy reviewed definitions
|
|
48
70
|
into an active contract through code review.
|
|
49
71
|
|
|
72
|
+
In Auto Boundary, Workbench presents the unresolved scope, field, aggregate,
|
|
73
|
+
relationship, privacy, and role-posture decisions together. Exact-digest
|
|
74
|
+
activation enables only the reviewed local authoring boundary. Generated named
|
|
75
|
+
capabilities still begin disabled and require their own activation.
|
|
76
|
+
|
|
50
77
|
## Input Safety
|
|
51
78
|
|
|
52
79
|
Input is bounded to 2 MiB, 50 objects, 128 fields per object, and 200 generated
|
|
@@ -39,17 +39,52 @@ The model does not receive:
|
|
|
39
39
|
- contract activation tools;
|
|
40
40
|
- approval tools;
|
|
41
41
|
- commit/writeback tools;
|
|
42
|
+
- MCP endpoint tokens, JWTs, refresh tokens, client secrets, or TLS keys;
|
|
42
43
|
- trusted tenant or principal authority as ordinary model arguments.
|
|
43
44
|
|
|
45
|
+
## Network transport and identity
|
|
46
|
+
|
|
47
|
+
Local stdio is the preferred one-client path. The MCP client launches Runner and
|
|
48
|
+
communicates over the child process's standard input/output, so Runner opens no
|
|
49
|
+
network listener and HTTP authentication does not apply.
|
|
50
|
+
|
|
51
|
+
A listening HTTP endpoint has no inherent client identity. Runner therefore
|
|
52
|
+
requires Bearer authentication by default. On loopback or an explicitly
|
|
53
|
+
single-tenant service, Bearer may carry one high-entropy opaque endpoint token;
|
|
54
|
+
that token only answers "may this service call the endpoint?" and is not a user
|
|
55
|
+
or tenant identity. Shared services require signed per-session JWTs, preferably
|
|
56
|
+
RS256/ES256 from an external identity provider, with exact issuer,
|
|
57
|
+
audience/resource, time, scope, tenant, and principal validation on every
|
|
58
|
+
request. An MCP session ID only routes protocol state and never authenticates a
|
|
59
|
+
caller.
|
|
60
|
+
|
|
61
|
+
Non-loopback HTTP refuses to bind unless Runner owns TLS, an explicitly trusted
|
|
62
|
+
TLS proxy protects the public and private hops, or an authenticated emergency
|
|
63
|
+
cleartext break-glass mode is selected. TLS protects the channel; mTLS also
|
|
64
|
+
authenticates a workload certificate but does not replace per-user/tenant claims
|
|
65
|
+
in a shared service. Exact Origin and Host policy, request/session limits, and
|
|
66
|
+
credential-pinned MCP sessions narrow browser, DNS-rebinding, exhaustion, and
|
|
67
|
+
session-swap attacks.
|
|
68
|
+
|
|
69
|
+
These controls remain independent: transport authentication permits endpoint
|
|
70
|
+
access; verified session claims provide tenant/principal context; the contract
|
|
71
|
+
authorizes semantic capabilities; database roles/RLS constrain the connection;
|
|
72
|
+
operator identity authorizes activation, approval, apply, reconcile, and
|
|
73
|
+
revert. See [HTTP MCP](http-mcp.md).
|
|
74
|
+
|
|
44
75
|
Reviewed aggregate tools expose one scalar only. Their function, column,
|
|
45
76
|
tenant key, optional equality selection, and minimum-group threshold are fixed
|
|
46
77
|
in the contract; the model receives no predicate arguments or member rows.
|
|
47
78
|
Suppression reduces single-record inference but does not replace statistical
|
|
48
79
|
privacy review. See [Bounded Aggregate Reads](aggregate-reads.md).
|
|
49
80
|
|
|
50
|
-
Trusted context comes from
|
|
51
|
-
|
|
52
|
-
|
|
81
|
+
Trusted context comes from operator-controlled process bindings for local or
|
|
82
|
+
explicitly single-tenant use, verified signed HTTP claims for a shared service,
|
|
83
|
+
or verified Cloud session context in Cloud mode. Tenant, principal, and
|
|
84
|
+
authorization scope must not be accepted from the model as authority.
|
|
85
|
+
Canonical `SESSION` content is rejected by Runner with
|
|
86
|
+
`SESSION_BINDING_UNSUPPORTED`; Runner never aliases it to environment values,
|
|
87
|
+
arbitrary headers, MCP metadata, or model arguments.
|
|
53
88
|
|
|
54
89
|
With a shared credential, these checks are application-level enforcement and
|
|
55
90
|
depend on Runner's fixed predicate implementation. Optional PostgreSQL RLS mode
|
package/docs/store-lifecycle.md
CHANGED
|
@@ -33,20 +33,108 @@ sqlite3 -readonly ./.synapsor/local.db '.tables'
|
|
|
33
33
|
Do not mutate the database directly. Its internal tables may change between
|
|
34
34
|
releases and are not a public storage API; use Runner commands for automation.
|
|
35
35
|
|
|
36
|
-
## Inspect
|
|
36
|
+
## Inspect one complete lifecycle
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
Start with no id:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
synapsor-runner lifecycle --store ./.synapsor/local.db
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`lifecycle`, `lifecycle show`, and `lifecycle show latest` are exact aliases.
|
|
45
|
+
They choose the newest proposal deterministically by `created_at` descending,
|
|
46
|
+
then proposal id as the tie-breaker. The default view answers:
|
|
47
|
+
|
|
48
|
+
- what capability and business object the model requested;
|
|
49
|
+
- which trusted tenant and principal scoped it;
|
|
50
|
+
- approval state and progress;
|
|
51
|
+
- whether a writeback job or intent exists;
|
|
52
|
+
- the latest guarded outcome and whether the source changed;
|
|
53
|
+
- replay and Cloud-link status; and
|
|
54
|
+
- the next safe read-only or operator command.
|
|
55
|
+
|
|
56
|
+
Use `--details` for one causal proposal-to-receipt/replay timeline, or `--json`
|
|
57
|
+
for one stable `synapsor.lifecycle-view.v1` JSON document:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
synapsor-runner lifecycle --details --store ./.synapsor/local.db
|
|
61
|
+
synapsor-runner lifecycle --json --store ./.synapsor/local.db
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The JSON is a domain view, not a dump of internal SQLite/PostgreSQL tables.
|
|
65
|
+
Absent stages are represented explicitly as empty arrays or `null`; Runner
|
|
66
|
+
does not fabricate a job, receipt, replay, or Cloud synchronization.
|
|
67
|
+
|
|
68
|
+
### Browse and filter without copying ids
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
synapsor-runner lifecycle list --limit 20 --store ./.synapsor/local.db
|
|
72
|
+
synapsor-runner lifecycle show --object invoice:INV-3001 --details --store ./.synapsor/local.db
|
|
73
|
+
synapsor-runner lifecycle list --tenant acme --capability billing.propose_late_fee_waiver
|
|
74
|
+
synapsor-runner lifecycle show --principal support-agent-17 --status applied
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
List/show support tenant, principal, capability, business object, status/state,
|
|
78
|
+
and time-window filters. Filtered `show` selects the newest match and reports
|
|
79
|
+
the total match count; use `lifecycle list` to browse all matches. List JSON
|
|
80
|
+
uses `synapsor.lifecycle-list.v1`.
|
|
81
|
+
|
|
82
|
+
### Start from any handle you already have
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
synapsor-runner lifecycle show wrp_... --details
|
|
86
|
+
synapsor-runner lifecycle show ev_... --details
|
|
87
|
+
synapsor-runner lifecycle show replay_... --details
|
|
88
|
+
synapsor-runner lifecycle show wbj_... --details
|
|
89
|
+
synapsor-runner lifecycle show wbi:wbj_... --details
|
|
90
|
+
synapsor-runner lifecycle show receipt:42 --details
|
|
91
|
+
synapsor-runner lifecycle show audit:17 --details
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Runner resolves proposal, evidence, replay, writeback-job, writeback-intent,
|
|
95
|
+
receipt, and query-audit handles to the owning proposal. Numeric receipt and
|
|
96
|
+
query-audit ids require the `receipt:` or `audit:` namespace; a bare number is
|
|
97
|
+
rejected rather than guessed.
|
|
98
|
+
|
|
99
|
+
Lifecycle inspection is read-only. It does not contact the source database or
|
|
100
|
+
Cloud, create a writeback job, acquire a worker lease, approve, apply, reconcile,
|
|
101
|
+
retry, or synchronize anything. It applies the same tenant/principal visibility
|
|
102
|
+
rules and secret/kept-out-field protections as the focused views.
|
|
103
|
+
|
|
104
|
+
### Shared PostgreSQL runtime store
|
|
105
|
+
|
|
106
|
+
For `storage.shared_postgres.mode = "runtime_store"`, pass the reviewed config:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
synapsor-runner lifecycle --config ./synapsor.runner.json
|
|
110
|
+
synapsor-runner lifecycle show --object invoice:INV-3001 --details \
|
|
111
|
+
--config ./synapsor.runner.json
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Runner reuses the existing bounded read bridge to the authoritative shared
|
|
115
|
+
ledger. It does not create or synchronize a persistent local mirror during
|
|
116
|
+
inspection. Connection failures fail safely; the command never falls back to an
|
|
117
|
+
unrelated local store.
|
|
118
|
+
|
|
119
|
+
## Focused inspection commands
|
|
120
|
+
|
|
121
|
+
The existing commands remain useful when you need one record type:
|
|
122
|
+
|
|
123
|
+
| Question | Focused command |
|
|
39
124
|
| --- | --- |
|
|
40
|
-
| What did the model propose? | `synapsor-runner proposals show
|
|
125
|
+
| What did the model propose? | `synapsor-runner proposals show latest --details` |
|
|
41
126
|
| What data supported it? | `synapsor-runner evidence list --proposal <proposal-id>` then `evidence show <evidence-id> --details` |
|
|
42
127
|
| 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
128
|
| Did guarded writeback apply? | `synapsor-runner receipts list --proposal <proposal-id>` |
|
|
45
|
-
| What
|
|
129
|
+
| What replay snapshot exists? | `synapsor-runner replay show latest --details` |
|
|
46
130
|
| What happened to one object? | `synapsor-runner activity search --object invoice:INV-3001` |
|
|
47
131
|
| What are the latest events? | `synapsor-runner events tail` |
|
|
48
132
|
| How large is the store? | `synapsor-runner store stats --store ./.synapsor/local.db` |
|
|
49
133
|
|
|
134
|
+
`proposals writeback-job` is intentionally absent from inspection examples:
|
|
135
|
+
it materializes a job and is therefore an operator mutation, not a read-only
|
|
136
|
+
view.
|
|
137
|
+
|
|
50
138
|
## Server leases
|
|
51
139
|
|
|
52
140
|
MCP server modes write a small lease file next to the store:
|
|
@@ -12,6 +12,104 @@ Use JSON for automation:
|
|
|
12
12
|
npx -y -p @synapsor/runner synapsor-runner doctor --first-run --json
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
+
## Fresh Start Did Not Enter Auto Boundary
|
|
16
|
+
|
|
17
|
+
Auto Boundary is the default only for a fresh interactive `start --from-env`
|
|
18
|
+
with no existing config, selector, answers file, machine-output flag, or other
|
|
19
|
+
automation input. This preserves every established 1.x route.
|
|
20
|
+
|
|
21
|
+
Check the generated state:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
synapsor-runner boundary status --json
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
To draft explicitly without prompts or a browser:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
synapsor-runner boundary draft \
|
|
31
|
+
--from-env DATABASE_URL \
|
|
32
|
+
--schema public \
|
|
33
|
+
--project-root . \
|
|
34
|
+
--json
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Scoped Explore Is Not Advertised
|
|
38
|
+
|
|
39
|
+
This is correct unless all authoring prerequisites pass. Explore is disabled by
|
|
40
|
+
default and never appears in production, unknown-profile, shared HTTP, remote,
|
|
41
|
+
or non-loopback `tools/list`.
|
|
42
|
+
|
|
43
|
+
Check:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
synapsor-runner boundary status --json
|
|
47
|
+
synapsor-runner boundary diff --json
|
|
48
|
+
synapsor-runner mcp status cursor --project
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The exact boundary digest must be active, the profile must explicitly be
|
|
52
|
+
`development` or `staging`, the generation lock and compiler/Spec versions
|
|
53
|
+
must be current, and the inspected database role must still be SELECT-only,
|
|
54
|
+
non-owner, non-superuser, and not `BYPASSRLS`. Runner also enforces a read-only
|
|
55
|
+
transaction for every Explore call.
|
|
56
|
+
|
|
57
|
+
A write-capable, owner, superuser, `BYPASSRLS`, or unverifiable credential may
|
|
58
|
+
still inspect metadata with a warning. It cannot enable source-row Explore.
|
|
59
|
+
Use a dedicated staging reader instead of weakening this check.
|
|
60
|
+
|
|
61
|
+
## Cursor Has Production Tools Instead Of Authoring Tools
|
|
62
|
+
|
|
63
|
+
Install the managed local authoring entry only after boundary activation:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
synapsor-runner mcp install cursor \
|
|
67
|
+
--project \
|
|
68
|
+
--authoring \
|
|
69
|
+
--project-root . \
|
|
70
|
+
--yes
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Authoring status reports exactly `app.describe_data` and `app.explore_data`.
|
|
74
|
+
After Protect and exact-digest activation, replace that entry with the
|
|
75
|
+
production config. The protected named capability remains available while
|
|
76
|
+
Explore disappears:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
synapsor-runner mcp install cursor \
|
|
80
|
+
--project \
|
|
81
|
+
--config ./synapsor.runner.json \
|
|
82
|
+
--store ./.synapsor/local.db \
|
|
83
|
+
--yes
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Boundary Became Stale After A Schema Or Grant Change
|
|
87
|
+
|
|
88
|
+
Generated authority is bound to the schema, role, grants, ownership, RLS
|
|
89
|
+
posture, compiler, and canonical Spec fingerprint:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
synapsor-runner boundary diff --json
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Additive fields receive no implicit authority. Breaking or posture drift fails
|
|
96
|
+
closed only for lock-bound generated authority. Regenerate, review the semantic
|
|
97
|
+
diff, and activate the new exact digest. Existing manually authored projects
|
|
98
|
+
without a generation lock retain their previous behavior.
|
|
99
|
+
|
|
100
|
+
## Aggregate Explore Suppressed Or Refused A Result
|
|
101
|
+
|
|
102
|
+
Suppression and budget failures are security behavior, not query failures.
|
|
103
|
+
Workbench shows the reviewed minimum cohort, maximum groups, response limits,
|
|
104
|
+
and durable extraction/differencing budgets. You cannot widen them in a model
|
|
105
|
+
argument.
|
|
106
|
+
|
|
107
|
+
Use a larger legitimate cohort or protect a narrower reviewed question. Do not
|
|
108
|
+
work around suppression with repeated slightly different filters: Runner
|
|
109
|
+
records normalized redacted plan metadata and exhausts the differencing budget.
|
|
110
|
+
Returned rows/groups, trusted tenant/principal values, credentials, and raw
|
|
111
|
+
sensitive literals are not stored in the query audit.
|
|
112
|
+
|
|
15
113
|
## Safe Action Draft Does Not Appear As A Tool
|
|
16
114
|
|
|
17
115
|
This is expected before activation. `start --action`, agent edits, `action
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Auto Boundary Churn Fixture
|
|
2
|
+
|
|
3
|
+
This synthetic staging fixture is the measured Runner 1.6.0 golden path:
|
|
4
|
+
PostgreSQL, Next.js, Prisma, Cursor-compatible local MCP, and the local
|
|
5
|
+
Workbench.
|
|
6
|
+
|
|
7
|
+
The database role is SELECT-only, non-owner, non-superuser, and subject to
|
|
8
|
+
forced tenant/principal RLS. The data includes reviewed churn cohorts, a
|
|
9
|
+
cross-tenant denial case, kept-out fields, and cohorts below the privacy
|
|
10
|
+
threshold.
|
|
11
|
+
|
|
12
|
+
Run the automated acceptance path from the repository root:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
corepack pnpm test:auto-boundary-explore
|
|
16
|
+
corepack pnpm test:auto-boundary-explore:packed
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The packed gate uses only packed public artifacts for the complete journey:
|
|
20
|
+
disabled whole-schema draft, digest activation, two Cursor authoring tools,
|
|
21
|
+
privacy-suppressed weekly churn analysis, denial and differencing tests,
|
|
22
|
+
Protect This Query, production Explore removal, and the surviving named
|
|
23
|
+
capability. No source row is mutated.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
services:
|
|
2
|
+
postgres:
|
|
3
|
+
image: postgres:16
|
|
4
|
+
environment:
|
|
5
|
+
POSTGRES_DB: synapsor_auto_boundary
|
|
6
|
+
POSTGRES_USER: synapsor_admin
|
|
7
|
+
POSTGRES_PASSWORD: synapsor_admin_password
|
|
8
|
+
ports:
|
|
9
|
+
- "127.0.0.1:55460:5432"
|
|
10
|
+
healthcheck:
|
|
11
|
+
test: ["CMD-SHELL", "test \"$(psql -U synapsor_admin -d synapsor_auto_boundary -tAc \"SELECT pg_postmaster_start_time() > initialized_at FROM public.synapsor_fixture_ready LIMIT 1\" 2>/dev/null | xargs)\" = t"]
|
|
12
|
+
interval: 1s
|
|
13
|
+
timeout: 3s
|
|
14
|
+
retries: 40
|
|
15
|
+
volumes:
|
|
16
|
+
- ./seed/postgres.sql:/docker-entrypoint-initdb.d/001-schema.sql:ro
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "synapsor-auto-boundary-churn",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "next dev"
|
|
7
|
+
},
|
|
8
|
+
"dependencies": {
|
|
9
|
+
"@prisma/client": "^6.0.0",
|
|
10
|
+
"next": "^15.0.0",
|
|
11
|
+
"react": "^19.0.0",
|
|
12
|
+
"react-dom": "^19.0.0"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"prisma": "^6.0.0",
|
|
16
|
+
"typescript": "^5.8.0"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
generator client {
|
|
2
|
+
provider = "prisma-client-js"
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
datasource db {
|
|
6
|
+
provider = "postgresql"
|
|
7
|
+
url = env("DATABASE_URL")
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
model Account {
|
|
11
|
+
id String @id
|
|
12
|
+
tenantId String @map("tenant_id")
|
|
13
|
+
ownerId String @map("owner_id")
|
|
14
|
+
region String
|
|
15
|
+
segment String
|
|
16
|
+
customerEmail String @map("customer_email")
|
|
17
|
+
internalRiskScore Int @map("internal_risk_score")
|
|
18
|
+
churnEvents ChurnEvent[]
|
|
19
|
+
|
|
20
|
+
@@map("accounts")
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
model ChurnEvent {
|
|
24
|
+
id String @id
|
|
25
|
+
tenantId String @map("tenant_id")
|
|
26
|
+
ownerId String @map("owner_id")
|
|
27
|
+
accountId String @map("account_id")
|
|
28
|
+
reasonCategory String @map("reason_category")
|
|
29
|
+
monthlyRevenueCents Int @map("monthly_revenue_cents")
|
|
30
|
+
churnedAt DateTime @map("churned_at") @db.Timestamptz(6)
|
|
31
|
+
privateNote String @map("private_note")
|
|
32
|
+
account Account @relation(fields: [accountId], references: [id])
|
|
33
|
+
|
|
34
|
+
@@index([tenantId, churnedAt])
|
|
35
|
+
@@map("churn_events")
|
|
36
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
CREATE TABLE public.accounts (
|
|
2
|
+
id text PRIMARY KEY,
|
|
3
|
+
tenant_id text NOT NULL,
|
|
4
|
+
owner_id text NOT NULL,
|
|
5
|
+
region text NOT NULL CHECK (region IN ('east', 'north', 'south', 'west')),
|
|
6
|
+
segment text NOT NULL CHECK (segment IN ('enterprise', 'growth', 'startup')),
|
|
7
|
+
customer_email text NOT NULL,
|
|
8
|
+
internal_risk_score integer NOT NULL
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
CREATE TABLE public.churn_events (
|
|
12
|
+
id text PRIMARY KEY,
|
|
13
|
+
tenant_id text NOT NULL,
|
|
14
|
+
owner_id text NOT NULL,
|
|
15
|
+
account_id text NOT NULL REFERENCES public.accounts(id) ON DELETE RESTRICT,
|
|
16
|
+
reason_category text NOT NULL CHECK (reason_category IN ('onboarding', 'price', 'product', 'service')),
|
|
17
|
+
monthly_revenue_cents integer NOT NULL CHECK (monthly_revenue_cents >= 0),
|
|
18
|
+
churned_at timestamptz NOT NULL,
|
|
19
|
+
private_note text NOT NULL
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
CREATE INDEX churn_events_tenant_time_idx
|
|
23
|
+
ON public.churn_events(tenant_id, churned_at);
|
|
24
|
+
|
|
25
|
+
WITH groups(prefix, tenant_id, owner_id, region, segment, reason_category, churned_at, group_size, revenue) AS (
|
|
26
|
+
VALUES
|
|
27
|
+
('p1-west-price', 'acme', 'pm-1', 'west', 'growth', 'price', '2026-06-03T12:00:00Z'::timestamptz, 5, 12000),
|
|
28
|
+
('p1-east-service', 'acme', 'pm-1', 'east', 'enterprise', 'service', '2026-06-10T12:00:00Z'::timestamptz, 5, 22000),
|
|
29
|
+
('p1-north-product', 'acme', 'pm-1', 'north', 'startup', 'product', '2026-06-17T12:00:00Z'::timestamptz, 2, 5000),
|
|
30
|
+
('p2-west-price', 'acme', 'pm-1', 'west', 'growth', 'price', '2026-07-08T12:00:00Z'::timestamptz, 10, 13000),
|
|
31
|
+
('p2-east-service', 'acme', 'pm-1', 'east', 'enterprise', 'service', '2026-07-15T12:00:00Z'::timestamptz, 7, 23000),
|
|
32
|
+
('p2-south-onboarding', 'acme', 'pm-1', 'south', 'startup', 'onboarding', '2026-07-22T12:00:00Z'::timestamptz, 5, 7000),
|
|
33
|
+
('p2-north-product', 'acme', 'pm-1', 'north', 'startup', 'product', '2026-07-22T12:00:00Z'::timestamptz, 1, 6000),
|
|
34
|
+
('other-west-price', 'globex', 'pm-2', 'west', 'enterprise', 'price', '2026-07-08T12:00:00Z'::timestamptz, 8, 99000)
|
|
35
|
+
)
|
|
36
|
+
INSERT INTO public.accounts (
|
|
37
|
+
id,
|
|
38
|
+
tenant_id,
|
|
39
|
+
owner_id,
|
|
40
|
+
region,
|
|
41
|
+
segment,
|
|
42
|
+
customer_email,
|
|
43
|
+
internal_risk_score
|
|
44
|
+
)
|
|
45
|
+
SELECT
|
|
46
|
+
prefix || '-' || item,
|
|
47
|
+
tenant_id,
|
|
48
|
+
owner_id,
|
|
49
|
+
region,
|
|
50
|
+
segment,
|
|
51
|
+
prefix || '-' || item || '@example.invalid',
|
|
52
|
+
900 + item
|
|
53
|
+
FROM groups
|
|
54
|
+
CROSS JOIN LATERAL generate_series(1, group_size) AS item;
|
|
55
|
+
|
|
56
|
+
WITH groups(prefix, tenant_id, owner_id, reason_category, churned_at, group_size, revenue) AS (
|
|
57
|
+
VALUES
|
|
58
|
+
('p1-west-price', 'acme', 'pm-1', 'price', '2026-06-03T12:00:00Z'::timestamptz, 5, 12000),
|
|
59
|
+
('p1-east-service', 'acme', 'pm-1', 'service', '2026-06-10T12:00:00Z'::timestamptz, 5, 22000),
|
|
60
|
+
('p1-north-product', 'acme', 'pm-1', 'product', '2026-06-17T12:00:00Z'::timestamptz, 2, 5000),
|
|
61
|
+
('p2-west-price', 'acme', 'pm-1', 'price', '2026-07-08T12:00:00Z'::timestamptz, 10, 13000),
|
|
62
|
+
('p2-east-service', 'acme', 'pm-1', 'service', '2026-07-15T12:00:00Z'::timestamptz, 7, 23000),
|
|
63
|
+
('p2-south-onboarding', 'acme', 'pm-1', 'onboarding', '2026-07-22T12:00:00Z'::timestamptz, 5, 7000),
|
|
64
|
+
('p2-north-product', 'acme', 'pm-1', 'product', '2026-07-22T12:00:00Z'::timestamptz, 1, 6000),
|
|
65
|
+
('other-west-price', 'globex', 'pm-2', 'price', '2026-07-08T12:00:00Z'::timestamptz, 8, 99000)
|
|
66
|
+
)
|
|
67
|
+
INSERT INTO public.churn_events (
|
|
68
|
+
id,
|
|
69
|
+
tenant_id,
|
|
70
|
+
owner_id,
|
|
71
|
+
account_id,
|
|
72
|
+
reason_category,
|
|
73
|
+
monthly_revenue_cents,
|
|
74
|
+
churned_at,
|
|
75
|
+
private_note
|
|
76
|
+
)
|
|
77
|
+
SELECT
|
|
78
|
+
'event-' || prefix || '-' || item,
|
|
79
|
+
tenant_id,
|
|
80
|
+
owner_id,
|
|
81
|
+
prefix || '-' || item,
|
|
82
|
+
reason_category,
|
|
83
|
+
revenue,
|
|
84
|
+
churned_at,
|
|
85
|
+
'synthetic kept-out note ' || prefix || '-' || item
|
|
86
|
+
FROM groups
|
|
87
|
+
CROSS JOIN LATERAL generate_series(1, group_size) AS item;
|
|
88
|
+
|
|
89
|
+
ALTER TABLE public.accounts ENABLE ROW LEVEL SECURITY;
|
|
90
|
+
ALTER TABLE public.accounts FORCE ROW LEVEL SECURITY;
|
|
91
|
+
ALTER TABLE public.churn_events ENABLE ROW LEVEL SECURITY;
|
|
92
|
+
ALTER TABLE public.churn_events FORCE ROW LEVEL SECURITY;
|
|
93
|
+
|
|
94
|
+
CREATE POLICY accounts_trusted_scope ON public.accounts
|
|
95
|
+
FOR SELECT
|
|
96
|
+
USING (
|
|
97
|
+
tenant_id = current_setting('app.tenant_id', true)
|
|
98
|
+
AND owner_id = current_setting('app.principal', true)
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
CREATE POLICY churn_events_trusted_scope ON public.churn_events
|
|
102
|
+
FOR SELECT
|
|
103
|
+
USING (
|
|
104
|
+
tenant_id = current_setting('app.tenant_id', true)
|
|
105
|
+
AND owner_id = current_setting('app.principal', true)
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
DO $$
|
|
109
|
+
BEGIN
|
|
110
|
+
CREATE ROLE synapsor_churn_reader LOGIN PASSWORD 'synapsor_churn_reader_password';
|
|
111
|
+
EXCEPTION WHEN duplicate_object THEN NULL;
|
|
112
|
+
END
|
|
113
|
+
$$;
|
|
114
|
+
|
|
115
|
+
ALTER ROLE synapsor_churn_reader SET default_transaction_read_only = on;
|
|
116
|
+
GRANT CONNECT ON DATABASE synapsor_auto_boundary TO synapsor_churn_reader;
|
|
117
|
+
GRANT USAGE ON SCHEMA public TO synapsor_churn_reader;
|
|
118
|
+
GRANT SELECT ON public.accounts, public.churn_events TO synapsor_churn_reader;
|
|
119
|
+
|
|
120
|
+
-- The image entrypoint runs these statements under a temporary postmaster and
|
|
121
|
+
-- then restarts PostgreSQL. Compose reports healthy only after that final
|
|
122
|
+
-- postmaster starts, preventing first-client disconnects in CI.
|
|
123
|
+
CREATE TABLE public.synapsor_fixture_ready (
|
|
124
|
+
initialized_at timestamptz NOT NULL
|
|
125
|
+
);
|
|
126
|
+
INSERT INTO public.synapsor_fixture_ready (initialized_at) VALUES (clock_timestamp());
|
|
@@ -40,7 +40,7 @@ OpenAI Agents Streamable HTTP example smoke passed.
|
|
|
40
40
|
export DATABASE_URL="<postgres-or-mysql-read-url>"
|
|
41
41
|
export SYNAPSOR_TENANT_ID="acme"
|
|
42
42
|
export SYNAPSOR_PRINCIPAL="openai_agent_demo"
|
|
43
|
-
export SYNAPSOR_RUNNER_HTTP_TOKEN="
|
|
43
|
+
export SYNAPSOR_RUNNER_HTTP_TOKEN="$(node -e 'process.stdout.write(require("node:crypto").randomBytes(32).toString("base64url"))')"
|
|
44
44
|
|
|
45
45
|
npx -y -p @synapsor/runner synapsor-runner mcp serve-streamable-http \
|
|
46
46
|
--config ./synapsor.runner.json \
|
|
@@ -49,6 +49,10 @@ npx -y -p @synapsor/runner synapsor-runner mcp serve-streamable-http \
|
|
|
49
49
|
--alias-mode openai
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
+
The operator must provision that same opaque value to the authorized client.
|
|
53
|
+
It is endpoint access for this loopback/single-service example, not user or
|
|
54
|
+
tenant identity. Do not put it in source code or the client config.
|
|
55
|
+
|
|
52
56
|
## Terminal 2: Run The Agent
|
|
53
57
|
|
|
54
58
|
```bash
|
|
@@ -58,7 +62,7 @@ pip install -r requirements.txt
|
|
|
58
62
|
|
|
59
63
|
export OPENAI_API_KEY="..."
|
|
60
64
|
export SYNAPSOR_RUNNER_HTTP_URL="http://127.0.0.1:8766/mcp"
|
|
61
|
-
|
|
65
|
+
read -rsp "Provisioned Synapsor endpoint token: " SYNAPSOR_MCP_ACCESS_TOKEN; printf '\n'; export SYNAPSOR_MCP_ACCESS_TOKEN
|
|
62
66
|
export SYNAPSOR_INVOICE_ID="INV-3001"
|
|
63
67
|
|
|
64
68
|
python agent.py
|
|
@@ -74,5 +78,7 @@ Expected behavior:
|
|
|
74
78
|
- no SQL/write/approval tool is exposed to the model;
|
|
75
79
|
- evidence/query audit are saved in the local Runner store.
|
|
76
80
|
|
|
77
|
-
For production-like deployment,
|
|
78
|
-
|
|
81
|
+
For production-like deployment, non-loopback HTTP requires Runner TLS or an
|
|
82
|
+
explicitly trusted TLS proxy. A shared user/tenant service uses a short-lived
|
|
83
|
+
signed access token from an external identity provider rather than distributing
|
|
84
|
+
one opaque token. See [HTTP MCP](../../docs/http-mcp.md).
|
|
@@ -13,14 +13,14 @@ except ImportError as exc:
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
async def main() -> None:
|
|
16
|
-
required = ["OPENAI_API_KEY", "SYNAPSOR_RUNNER_HTTP_URL", "
|
|
16
|
+
required = ["OPENAI_API_KEY", "SYNAPSOR_RUNNER_HTTP_URL", "SYNAPSOR_MCP_ACCESS_TOKEN"]
|
|
17
17
|
missing = [name for name in required if not os.environ.get(name)]
|
|
18
18
|
if missing:
|
|
19
19
|
raise SystemExit(f"Missing required environment variables: {', '.join(missing)}")
|
|
20
20
|
|
|
21
21
|
invoice_id = os.environ.get("SYNAPSOR_INVOICE_ID", "INV-3001")
|
|
22
22
|
mcp_url = os.environ["SYNAPSOR_RUNNER_HTTP_URL"]
|
|
23
|
-
token = os.environ["
|
|
23
|
+
token = os.environ["SYNAPSOR_MCP_ACCESS_TOKEN"]
|
|
24
24
|
|
|
25
25
|
async with MCPServerStreamableHttp(
|
|
26
26
|
params={
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
FROM node:22-bookworm-slim
|
|
2
2
|
|
|
3
|
+
RUN apt-get update \
|
|
4
|
+
&& apt-get install -y --no-install-recommends openssl \
|
|
5
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
6
|
+
|
|
3
7
|
RUN corepack enable && corepack prepare pnpm@10.14.0 --activate
|
|
4
8
|
|
|
5
9
|
WORKDIR /app
|
|
6
10
|
COPY . .
|
|
7
11
|
RUN corepack pnpm install --frozen-lockfile \
|
|
8
|
-
&& corepack pnpm build:runner-package
|
|
12
|
+
&& corepack pnpm build:runner-package \
|
|
13
|
+
&& chmod +x examples/runner-fleet/start-tls-runner.sh
|
|
9
14
|
|
|
10
|
-
ENTRYPOINT ["
|
|
15
|
+
ENTRYPOINT ["examples/runner-fleet/start-tls-runner.sh"]
|
|
11
16
|
CMD ["mcp", "serve", "--transport", "streamable-http", "--host", "0.0.0.0", "--port", "8766", "--config", "/config/synapsor.runner.json"]
|