@synapsor/runner 1.6.2 → 1.6.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/AGENTS.md +2 -2
- package/CHANGELOG.md +141 -2
- package/CONTRIBUTING.md +10 -3
- package/README.md +65 -83
- package/SECURITY.md +14 -2
- package/THREAT_MODEL.md +64 -4
- package/dist/authoring.mjs +151 -16
- package/dist/cli.d.ts +31 -3
- package/dist/cli.d.ts.map +1 -1
- package/dist/local-ui.d.ts +145 -1
- package/dist/local-ui.d.ts.map +1 -1
- package/dist/runner.mjs +37488 -19009
- package/dist/runtime.mjs +4056 -235
- package/dist/shadow.mjs +2423 -65
- package/docs/README.md +36 -2
- package/docs/agent-guided-setup.md +239 -0
- package/docs/aggregate-reads.md +14 -5
- package/docs/app-owned-executors.md +5 -6
- package/docs/approval-roles-and-operator-identity.md +353 -0
- package/docs/auto-boundary-and-scoped-explore.md +89 -11
- package/docs/capability-authoring.md +21 -2
- package/docs/client-recipes.md +1 -1
- package/docs/cloud-mode.md +7 -7
- package/docs/conformance.md +6 -3
- package/docs/current-scope.md +32 -2
- package/docs/cursor-plugin.md +3 -3
- package/docs/database-enforced-scope.md +6 -0
- package/docs/dsl-json-parity.md +75 -0
- package/docs/dsl-reference.md +22 -1
- package/docs/fresh-developer-usability.md +155 -80
- package/docs/getting-started-own-database.md +45 -44
- package/docs/guarded-crud-writeback.md +19 -0
- package/docs/guided-onboarding.md +399 -0
- package/docs/human-attention-notifications.md +367 -0
- package/docs/limitations.md +49 -13
- package/docs/local-mode.md +36 -22
- package/docs/mcp-audit.md +14 -14
- package/docs/mcp-client-setup.md +11 -14
- package/docs/mcp-clients.md +1 -1
- package/docs/migrating-to-synapsor-spec.md +42 -0
- package/docs/openai-agents-sdk.md +2 -2
- package/docs/production.md +39 -0
- package/docs/proposal-evidence-freshness.md +7 -7
- package/docs/recipes.md +6 -6
- package/docs/release-notes.md +144 -4
- package/docs/release-policy.md +20 -7
- package/docs/reversible-change-sets.md +1 -1
- package/docs/reviewed-database-views.md +132 -0
- package/docs/reviewed-relationships.md +245 -0
- package/docs/rfcs/006-reviewed-reversible-change-sets.md +1 -1
- package/docs/runner-bundles.md +7 -7
- package/docs/runner-config-reference.md +132 -0
- package/docs/running-a-runner-fleet.md +16 -1
- package/docs/schema-api-candidates.md +1 -1
- package/docs/security-boundary.md +54 -0
- package/docs/store-lifecycle.md +20 -1
- package/docs/supervised-automatic-apply.md +267 -0
- package/docs/troubleshooting-first-run.md +85 -10
- package/docs/use-your-own-database.md +6 -4
- package/docs/workbench-ask.md +279 -0
- package/docs/writeback-executors.md +7 -7
- package/examples/auto-boundary-churn/README.md +1 -1
- package/examples/community-solar-clean-room/README.md +7 -0
- package/examples/community-solar-clean-room/docker-compose.yml +16 -0
- package/examples/community-solar-clean-room/package.json +15 -0
- package/examples/community-solar-clean-room/prisma/schema.prisma +171 -0
- package/examples/community-solar-clean-room/seed/postgres.sql +571 -0
- package/examples/fitflow-guided-onboarding/README.md +43 -0
- package/examples/fitflow-guided-onboarding/app/page.tsx +8 -0
- package/examples/fitflow-guided-onboarding/docker-compose.yml +16 -0
- package/examples/fitflow-guided-onboarding/package.json +18 -0
- package/examples/fitflow-guided-onboarding/prisma/schema.prisma +98 -0
- package/examples/fitflow-guided-onboarding/seed/postgres.sql +401 -0
- package/examples/openai-agents-http/Makefile +1 -1
- package/examples/openai-agents-http/README.md +1 -1
- package/examples/openai-agents-stdio/Makefile +1 -1
- package/examples/openai-agents-stdio/README.md +2 -2
- package/examples/openai-agents-stdio/agent.py +1 -4
- package/examples/operator-oidc/issuer.mjs +188 -0
- package/examples/reference-support-billing-app/README.md +16 -16
- package/examples/retail-clean-room/README.md +7 -0
- package/examples/retail-clean-room/docker-compose.yml +16 -0
- package/examples/retail-clean-room/package.json +9 -0
- package/examples/retail-clean-room/prisma/schema.prisma +239 -0
- package/examples/retail-clean-room/seed/postgres.sql +781 -0
- package/examples/retail-clean-room/view-recipe/average-retained-revenue.synapsor.sql +21 -0
- package/examples/retail-clean-room/view-recipe/synapsor.runner.json +25 -0
- package/examples/support-plan-credit/README.md +6 -2
- package/examples/support-plan-credit/mcp-client-examples/claude-code.sh +1 -4
- package/examples/support-plan-credit/mcp-client-examples/claude-desktop.json +1 -4
- package/examples/support-plan-credit/mcp-client-examples/codex.config.toml +1 -4
- package/examples/support-plan-credit/mcp-client-examples/cursor-global.mcp.json +1 -4
- package/examples/support-plan-credit/mcp-client-examples/cursor-project.mcp.json +1 -4
- package/examples/support-plan-credit/mcp-client-examples/generic-stdio.json +1 -4
- package/examples/support-plan-credit/mcp-client-examples/generic-stdio.mjs +1 -1
- package/examples/support-plan-credit/mcp-client-examples/google-adk.py +1 -1
- package/examples/support-plan-credit/mcp-client-examples/langchain.mjs +1 -1
- package/examples/support-plan-credit/mcp-client-examples/llamaindex.py +1 -1
- package/examples/support-plan-credit/mcp-client-examples/openai-agents-stdio.ts +1 -1
- package/examples/support-plan-credit/mcp-client-examples/vscode.mcp.json +1 -4
- package/fixtures/compatibility/published-1.6.3/manifest.json +76 -0
- package/fixtures/compatibility/published-1.6.3/sources/packages/dsl/examples/aggregate-read.synapsor.sql +21 -0
- package/fixtures/compatibility/published-1.6.3/sources/packages/dsl/examples/billing-late-fee.synapsor.sql +56 -0
- package/fixtures/compatibility/published-1.6.3/sources/packages/dsl/examples/bounded-set-multi-term.synapsor.sql +30 -0
- package/fixtures/compatibility/published-1.6.3/sources/packages/dsl/examples/principal-row-scope.synapsor.sql +23 -0
- package/fixtures/compatibility/published-1.6.3/sources/packages/spec/fixtures/conformance/aggregate-read/contract.json +119 -0
- package/fixtures/compatibility/published-1.6.3/sources/packages/spec/fixtures/conformance/approval-quorum/contract.json +44 -0
- package/fixtures/compatibility/published-1.6.3/sources/packages/spec/fixtures/conformance/bounded-set-threats/contract.json +115 -0
- package/fixtures/compatibility/published-1.6.3/sources/packages/spec/fixtures/conformance/principal-row-scope/contract.json +78 -0
- package/fixtures/compatibility/published-1.6.3/sources/packages/spec/fixtures/conformance/proposal-capability/contract.json +101 -0
- package/fixtures/compatibility/published-1.6.3/sources/packages/spec/fixtures/conformance/reversible-change-sets/contract.json +98 -0
- package/fixtures/compatibility/published-1.6.3/sources/packages/spec/fixtures/valid/basic-read.contract.json +60 -0
- package/llms.txt +10 -5
- package/package.json +11 -3
- package/schemas/schema-candidate-review.schema.json +42 -0
- package/schemas/synapsor.runner.schema.json +227 -4
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Reviewed Database Views For Derived Measures
|
|
2
|
+
|
|
3
|
+
Scoped Explore deliberately has no formula or expression language. The model
|
|
4
|
+
cannot invent ratios, arithmetic, functions, subqueries, or joins. When a
|
|
5
|
+
useful measure needs reviewed database logic, put that logic in a
|
|
6
|
+
database-owned view and expose only the resulting typed column through a named
|
|
7
|
+
Synapsor capability.
|
|
8
|
+
|
|
9
|
+
The authority split is:
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
Database owner reviews formula, joins, cardinality, and base-table scope
|
|
13
|
+
-> view publishes a small typed row shape
|
|
14
|
+
-> contract author reviews the view columns and fixed predicate
|
|
15
|
+
-> Runner applies trusted scope and the named aggregate
|
|
16
|
+
-> model receives one scalar, never SQL or member rows
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
This is an explicit escape hatch, not automatic generation. Auto Boundary
|
|
20
|
+
blocks a view whose row identity cannot be proven from catalog metadata. Review
|
|
21
|
+
the view definition, scope, uniqueness, and grants before hand-authoring a
|
|
22
|
+
capability.
|
|
23
|
+
|
|
24
|
+
## PostgreSQL Hardened Pattern
|
|
25
|
+
|
|
26
|
+
Use a `security_invoker` view so PostgreSQL checks permissions and row-level
|
|
27
|
+
security (RLS) as the Runner read role, not as the view owner. A
|
|
28
|
+
`security_barrier` also prevents unsafe predicate movement across the view
|
|
29
|
+
boundary.
|
|
30
|
+
|
|
31
|
+
The retail clean-room fixture defines this reviewed ratio:
|
|
32
|
+
|
|
33
|
+
```sql
|
|
34
|
+
CREATE VIEW public.reviewed_order_performance
|
|
35
|
+
WITH (security_barrier = true, security_invoker = true) AS
|
|
36
|
+
SELECT
|
|
37
|
+
orders.id AS order_id,
|
|
38
|
+
orders.merchant_id,
|
|
39
|
+
orders.assigned_manager_id,
|
|
40
|
+
orders.region_id,
|
|
41
|
+
ROUND(
|
|
42
|
+
(orders.net_revenue_cents::numeric /
|
|
43
|
+
NULLIF(orders.gross_revenue_cents, 0)) * 10000
|
|
44
|
+
)::integer AS net_revenue_retention_basis_points
|
|
45
|
+
FROM public.orders;
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The formula is fixed in a reviewed database migration. The model sees only
|
|
49
|
+
`net_revenue_retention_basis_points`. It cannot change the numerator,
|
|
50
|
+
denominator, scale, or source relation.
|
|
51
|
+
|
|
52
|
+
For `database_scope.mode = "postgres_rls"`, Runner accepts a read view only
|
|
53
|
+
when all of these checks pass:
|
|
54
|
+
|
|
55
|
+
- the target is a normal PostgreSQL view used for `SELECT`;
|
|
56
|
+
- both `security_invoker=true` and `security_barrier=true` are present;
|
|
57
|
+
- the Runner role is not the view owner, a superuser, or `BYPASSRLS`;
|
|
58
|
+
- every referenced table or nested view can be resolved from the catalog;
|
|
59
|
+
- every referenced base table independently passes the existing FORCE RLS,
|
|
60
|
+
role, policy-operation, and trusted-setting checks;
|
|
61
|
+
- every nested view passes the same view checks.
|
|
62
|
+
|
|
63
|
+
An ordinary owner-rights view, an unverifiable dependency, a missing RLS
|
|
64
|
+
setting, or any non-read operation fails closed before MCP serving.
|
|
65
|
+
|
|
66
|
+
## Named Aggregate
|
|
67
|
+
|
|
68
|
+
The public DSL fixes the trusted scope, formula output, region, aggregate, and
|
|
69
|
+
minimum cohort:
|
|
70
|
+
|
|
71
|
+
```sql
|
|
72
|
+
CREATE AGENT CONTEXT retail_analytics_context
|
|
73
|
+
BIND tenant_id FROM ENVIRONMENT SYNAPSOR_TENANT_ID REQUIRED
|
|
74
|
+
BIND principal FROM ENVIRONMENT SYNAPSOR_PRINCIPAL REQUIRED
|
|
75
|
+
TENANT BINDING tenant_id
|
|
76
|
+
PRINCIPAL BINDING principal
|
|
77
|
+
END
|
|
78
|
+
|
|
79
|
+
CREATE CAPABILITY retail.average_net_revenue_retention_pacific
|
|
80
|
+
DESCRIPTION 'Return the privacy-suppressed average net-to-gross revenue retention rate for Pacific orders assigned to the trusted manager.'
|
|
81
|
+
RETURNS HINT 'Returns one scalar average in basis points or a suppression result; never order rows.'
|
|
82
|
+
USING CONTEXT retail_analytics_context
|
|
83
|
+
SOURCE retail_postgres
|
|
84
|
+
ON public.reviewed_order_performance
|
|
85
|
+
PRIMARY KEY order_id
|
|
86
|
+
TENANT KEY merchant_id
|
|
87
|
+
PRINCIPAL SCOPE KEY assigned_manager_id
|
|
88
|
+
AGGREGATE READ AVG net_revenue_retention_basis_points
|
|
89
|
+
SELECT WHERE region_id = 'region-pacific'
|
|
90
|
+
MIN GROUP SIZE 5
|
|
91
|
+
REQUIRE EVIDENCE
|
|
92
|
+
END
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Validate, compile, validate the Runner config, and serve:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
npx -y @synapsor/runner dsl validate \
|
|
99
|
+
./average-retained-revenue.synapsor.sql --strict
|
|
100
|
+
|
|
101
|
+
npx -y @synapsor/runner dsl compile \
|
|
102
|
+
./average-retained-revenue.synapsor.sql \
|
|
103
|
+
--out ./average-retained-revenue.contract.json \
|
|
104
|
+
--strict
|
|
105
|
+
|
|
106
|
+
npx -y @synapsor/runner config validate \
|
|
107
|
+
--config ./synapsor.runner.json
|
|
108
|
+
|
|
109
|
+
npx -y @synapsor/runner mcp serve \
|
|
110
|
+
--config ./synapsor.runner.json
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The runnable packaged recipe is under
|
|
114
|
+
`examples/retail-clean-room/view-recipe/`. Its clean-room gate proves that the
|
|
115
|
+
result is exactly `9000` basis points for the trusted fixture scope, includes
|
|
116
|
+
no member rows, and does not change the source database.
|
|
117
|
+
|
|
118
|
+
## Review Checklist
|
|
119
|
+
|
|
120
|
+
1. Prove the view has one intended row per counted entity.
|
|
121
|
+
2. Prove joins cannot multiply rows or silently double-count values.
|
|
122
|
+
3. Apply tenant and, where required, principal scope to every base relation.
|
|
123
|
+
4. Keep sensitive source columns out of the view output.
|
|
124
|
+
5. Grant the Runner role only `SELECT` on the view and required base objects.
|
|
125
|
+
6. Test same-tenant other-principal and cross-tenant denial.
|
|
126
|
+
7. Test zero denominators, nulls, rounding, and numeric overflow.
|
|
127
|
+
8. Keep cohort suppression and result limits in the Synapsor capability.
|
|
128
|
+
9. Treat a changed view definition as reviewed authority drift.
|
|
129
|
+
|
|
130
|
+
For complex or ambiguous calculations, keep the capability disabled until the
|
|
131
|
+
database migration, contract, and expected results have all been reviewed
|
|
132
|
+
together.
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
# Reviewed Relationship Paths
|
|
2
|
+
|
|
3
|
+
Synapsor Runner can answer bounded aggregate questions across related tables
|
|
4
|
+
without exposing a general join planner or SQL. The relationship is authority:
|
|
5
|
+
Runner proposes it from database catalog evidence, a human reviews it, and the
|
|
6
|
+
activated boundary fixes its exact path and semantics.
|
|
7
|
+
|
|
8
|
+
This feature is part of local, development/staging-only Scoped Explore.
|
|
9
|
+
Production receives only named capabilities created through Protect This Query.
|
|
10
|
+
|
|
11
|
+
## The Safety Rule
|
|
12
|
+
|
|
13
|
+
A model may select an already activated relationship **by name**. It cannot
|
|
14
|
+
provide a table name, join key, join type, relationship path, or SQL fragment.
|
|
15
|
+
|
|
16
|
+
Runner accepts only:
|
|
17
|
+
|
|
18
|
+
- one to three relationship paths in a protected capability;
|
|
19
|
+
- one or two links per path;
|
|
20
|
+
- inspected foreign-key links whose target key is unique;
|
|
21
|
+
- `many_to_one` cardinality with `max_fan_out: 1`;
|
|
22
|
+
- trusted tenant and, where configured, principal scope on every relation;
|
|
23
|
+
- an explicit human choice for missing related rows.
|
|
24
|
+
|
|
25
|
+
Runner refuses:
|
|
26
|
+
|
|
27
|
+
- one-to-many and many-to-many paths;
|
|
28
|
+
- relationships inferred only from similar column names;
|
|
29
|
+
- ambiguous or unproven cardinality;
|
|
30
|
+
- more than two links in one path;
|
|
31
|
+
- a plan using more than three reviewed paths;
|
|
32
|
+
- a relationship whose catalog proof or generation lock is stale;
|
|
33
|
+
- a related table whose trusted scope cannot be enforced;
|
|
34
|
+
- formulas, derived measures, free-form joins, and model-selected identifiers.
|
|
35
|
+
|
|
36
|
+
These limits permit reviewed star-style analysis. For example, a sales fact can
|
|
37
|
+
group by an activated store path and an activated category path without
|
|
38
|
+
multiplying the counted sales rows.
|
|
39
|
+
|
|
40
|
+
## Why Fan-Out Matters
|
|
41
|
+
|
|
42
|
+
Suppose one `sales_facts` row points to one store. Joining the store cannot
|
|
43
|
+
create more sales rows, so the relationship is many-to-one and has a maximum
|
|
44
|
+
fan-out of one.
|
|
45
|
+
|
|
46
|
+
By contrast, joining a sale to all of its line items can turn one sale into
|
|
47
|
+
several rows. A naive `SUM(sales_facts.amount_cents)` would then overstate
|
|
48
|
+
revenue. Runner refuses that one-to-many path instead of silently returning a
|
|
49
|
+
plausible but wrong number.
|
|
50
|
+
|
|
51
|
+
## Demand-Driven Review
|
|
52
|
+
|
|
53
|
+
Auto Boundary may discover more proven relationships than a developer needs.
|
|
54
|
+
They are not all activated automatically.
|
|
55
|
+
|
|
56
|
+
The intended flow is:
|
|
57
|
+
|
|
58
|
+
1. Activate the small starter boundary.
|
|
59
|
+
2. Ask a bounded aggregate question.
|
|
60
|
+
3. If the question needs an inactive but catalog-proven path, Runner refuses the
|
|
61
|
+
plan and identifies the exact path and proof.
|
|
62
|
+
4. Workbench shows that one relationship for operator review.
|
|
63
|
+
5. The operator chooses missing-row semantics when required and activates the
|
|
64
|
+
exact new boundary digest.
|
|
65
|
+
6. Retry the original question.
|
|
66
|
+
|
|
67
|
+
The model cannot trigger step 5. Adding the path creates new authority, so it is
|
|
68
|
+
an operator-plane action with a new digest. Existing unrelated confirmations
|
|
69
|
+
remain valid; only dependent review state is invalidated.
|
|
70
|
+
|
|
71
|
+
## Nullable Links
|
|
72
|
+
|
|
73
|
+
An optional foreign key requires an explicit business decision:
|
|
74
|
+
|
|
75
|
+
- `UNMATCHED EXCLUDE`: omit a counted row when its related record is missing;
|
|
76
|
+
- `UNMATCHED KEEP NULL`: keep the counted row and return an empty/null group
|
|
77
|
+
value for the missing relationship.
|
|
78
|
+
|
|
79
|
+
This is not cosmetic. The choice can change totals and cohort sizes, so Runner
|
|
80
|
+
does not silently choose it. The decision is stored in the canonical contract
|
|
81
|
+
and bound into its digest.
|
|
82
|
+
|
|
83
|
+
## DSL Syntax
|
|
84
|
+
|
|
85
|
+
The SQL-like Domain-Specific Language (DSL) supports a legacy one-hop form and
|
|
86
|
+
the additive path form. Existing legacy contracts retain their canonical bytes
|
|
87
|
+
and digest.
|
|
88
|
+
|
|
89
|
+
Legacy one-hop form:
|
|
90
|
+
|
|
91
|
+
```sql
|
|
92
|
+
PROTECTED RELATIONSHIP store
|
|
93
|
+
ON store_id
|
|
94
|
+
REFERENCES public.stores.id
|
|
95
|
+
PRIMARY KEY id
|
|
96
|
+
TENANT KEY tenant_id
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Reviewed path form:
|
|
100
|
+
|
|
101
|
+
```sql
|
|
102
|
+
PROTECTED RELATIONSHIP store LINK 1
|
|
103
|
+
ON store_id
|
|
104
|
+
REFERENCES public.stores.id
|
|
105
|
+
PRIMARY KEY id
|
|
106
|
+
TENANT KEY tenant_id
|
|
107
|
+
UNMATCHED EXCLUDE
|
|
108
|
+
|
|
109
|
+
PROTECTED RELATIONSHIP category LINK 1
|
|
110
|
+
ON product_id
|
|
111
|
+
REFERENCES public.products.id
|
|
112
|
+
PRIMARY KEY id
|
|
113
|
+
TENANT KEY tenant_id
|
|
114
|
+
UNMATCHED KEEP NULL
|
|
115
|
+
|
|
116
|
+
PROTECTED RELATIONSHIP category LINK 2
|
|
117
|
+
ON category_id
|
|
118
|
+
REFERENCES public.categories.id
|
|
119
|
+
PRIMARY KEY id
|
|
120
|
+
TENANT KEY tenant_id
|
|
121
|
+
UNMATCHED EXCLUDE
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
In that example:
|
|
125
|
+
|
|
126
|
+
- `store` and `category` are user-defined path names;
|
|
127
|
+
- `LINK 1` and `LINK 2` are DSL keywords and ordered link numbers;
|
|
128
|
+
- `store_id`, `product_id`, and `category_id` are inspected local keys;
|
|
129
|
+
- `public.stores.id`, `public.products.id`, and `public.categories.id` are
|
|
130
|
+
fixed reviewed targets;
|
|
131
|
+
- `PRIMARY KEY`, `TENANT KEY`, `UNMATCHED EXCLUDE`, and
|
|
132
|
+
`UNMATCHED KEEP NULL` are DSL syntax;
|
|
133
|
+
- the two `category` declarations form one depth-two path.
|
|
134
|
+
|
|
135
|
+
Link declarations for one path must be contiguous and ordered. A protected read
|
|
136
|
+
cannot mix the legacy form with the path form.
|
|
137
|
+
|
|
138
|
+
## Canonical Contract
|
|
139
|
+
|
|
140
|
+
The path form compiles to public, language-neutral JSON:
|
|
141
|
+
|
|
142
|
+
```json
|
|
143
|
+
{
|
|
144
|
+
"protected_read": {
|
|
145
|
+
"mode": "aggregate",
|
|
146
|
+
"relationships": [
|
|
147
|
+
{
|
|
148
|
+
"name": "category",
|
|
149
|
+
"links": [
|
|
150
|
+
{
|
|
151
|
+
"local_key": "product_id",
|
|
152
|
+
"schema": "public",
|
|
153
|
+
"table": "products",
|
|
154
|
+
"target_key": "id",
|
|
155
|
+
"primary_key": "id",
|
|
156
|
+
"tenant_key": "tenant_id",
|
|
157
|
+
"cardinality": "many_to_one",
|
|
158
|
+
"max_fan_out": 1,
|
|
159
|
+
"unmatched_rows": "keep_null"
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
"local_key": "category_id",
|
|
163
|
+
"schema": "public",
|
|
164
|
+
"table": "categories",
|
|
165
|
+
"target_key": "id",
|
|
166
|
+
"primary_key": "id",
|
|
167
|
+
"tenant_key": "tenant_id",
|
|
168
|
+
"cardinality": "many_to_one",
|
|
169
|
+
"max_fan_out": 1,
|
|
170
|
+
"unmatched_rows": "exclude"
|
|
171
|
+
}
|
|
172
|
+
]
|
|
173
|
+
}
|
|
174
|
+
]
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Measures, dimensions, time buckets, and fixed predicates refer to the reviewed
|
|
180
|
+
path name. The canonical Spec is not a generic SQL or analytics Abstract Syntax
|
|
181
|
+
Tree (AST); it represents fixed reviewed authority.
|
|
182
|
+
|
|
183
|
+
## Scope And Privacy
|
|
184
|
+
|
|
185
|
+
For every participating relation, Runner injects trusted tenant and configured
|
|
186
|
+
principal predicates outside model arguments. Scope on only the starting table
|
|
187
|
+
is insufficient.
|
|
188
|
+
|
|
189
|
+
Aggregate privacy controls continue to apply after relationships are added:
|
|
190
|
+
|
|
191
|
+
- minimum cohort suppression;
|
|
192
|
+
- maximum groups and response size;
|
|
193
|
+
- query, rate, extraction, and differencing budgets;
|
|
194
|
+
- fixed reviewed dimensions and measures;
|
|
195
|
+
- no pagination that bypasses the group bound.
|
|
196
|
+
|
|
197
|
+
Adding dimensions can make cohorts smaller, so suppression is evaluated on the
|
|
198
|
+
final grouped result. A relationship does not weaken kept-out fields: kept-out
|
|
199
|
+
fields cannot be selected, filtered, grouped, sorted, joined, or counted
|
|
200
|
+
distinctly.
|
|
201
|
+
|
|
202
|
+
## Protect For Production
|
|
203
|
+
|
|
204
|
+
Protect This Query freezes the exact reviewed paths, measures, dimensions,
|
|
205
|
+
filters, ordering, limits, scope, and privacy controls into a disabled named
|
|
206
|
+
capability. A human activates its exact digest. Scoped Explore can then be
|
|
207
|
+
disabled; the protected capability remains available.
|
|
208
|
+
|
|
209
|
+
Production does not advertise broad Explore tools and does not permit the model
|
|
210
|
+
to add relationship authority.
|
|
211
|
+
|
|
212
|
+
## When A View Is Better
|
|
213
|
+
|
|
214
|
+
Runner intentionally does not support:
|
|
215
|
+
|
|
216
|
+
- arbitrary relationship graphs;
|
|
217
|
+
- many-to-many analytics;
|
|
218
|
+
- derived formulas such as `revenue / capacity`;
|
|
219
|
+
- model-defined expressions;
|
|
220
|
+
- general-purpose join planning.
|
|
221
|
+
|
|
222
|
+
For those cases, create a database view that computes the reviewed business
|
|
223
|
+
meaning, grant Runner's reader access only to the safe projected columns, and
|
|
224
|
+
inspect the view as a new resource. See [Reviewed Database
|
|
225
|
+
Views](reviewed-database-views.md).
|
|
226
|
+
|
|
227
|
+
This keeps business formulas and complex joins under ordinary database and code
|
|
228
|
+
review while preserving Synapsor's field, scope, budget, proposal, and audit
|
|
229
|
+
boundary.
|
|
230
|
+
|
|
231
|
+
## Verification
|
|
232
|
+
|
|
233
|
+
The repository release gates exercise:
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
corepack pnpm test:reviewed-relationships
|
|
237
|
+
corepack pnpm test:auto-boundary-explore:packed
|
|
238
|
+
corepack pnpm test:clean-room:community-solar
|
|
239
|
+
corepack pnpm test:clean-room:retail
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
The live PostgreSQL and MySQL relationship test proves direct star paths,
|
|
243
|
+
depth-two paths, both nullable-link choices, demand-driven activation, drift
|
|
244
|
+
refusal, per-relation tenant/principal scope, suppression, and rejection of a
|
|
245
|
+
deliberately wrong fan-out relationship.
|
package/docs/runner-bundles.md
CHANGED
|
@@ -41,11 +41,11 @@ cd ./synapsor-runner-bundle
|
|
|
41
41
|
cp .env.example .env
|
|
42
42
|
set -a && . ./.env && set +a
|
|
43
43
|
|
|
44
|
-
npx -y
|
|
45
|
-
npx -y
|
|
46
|
-
npx -y
|
|
47
|
-
npx -y
|
|
48
|
-
npx -y
|
|
44
|
+
npx -y @synapsor/runner contract validate ./synapsor.contract.json
|
|
45
|
+
npx -y @synapsor/runner config validate --config ./synapsor.runner.json
|
|
46
|
+
npx -y @synapsor/runner tools preview --config ./synapsor.runner.json --store ./.synapsor/local.db
|
|
47
|
+
npx -y @synapsor/runner cloud connect --config ./synapsor.cloud.json
|
|
48
|
+
npx -y @synapsor/runner mcp serve --config ./synapsor.runner.json --store ./.synapsor/local.db
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
For proposal contracts, run the trusted worker in a separate
|
|
@@ -53,14 +53,14 @@ operator-controlled terminal:
|
|
|
53
53
|
|
|
54
54
|
```bash
|
|
55
55
|
set -a && . ./.env && set +a
|
|
56
|
-
npx -y
|
|
56
|
+
npx -y @synapsor/runner start --config ./synapsor.runner.json --store ./.synapsor/local.db
|
|
57
57
|
```
|
|
58
58
|
|
|
59
59
|
After an MCP tool creates a local proposal, sync its reviewed diff and safe
|
|
60
60
|
references to Cloud:
|
|
61
61
|
|
|
62
62
|
```bash
|
|
63
|
-
npx -y
|
|
63
|
+
npx -y @synapsor/runner cloud sync latest \
|
|
64
64
|
--config ./synapsor.cloud.json \
|
|
65
65
|
--store ./.synapsor/local.db
|
|
66
66
|
```
|
|
@@ -40,6 +40,9 @@ Unknown keys fail when `strict` is true (the default).
|
|
|
40
40
|
| `rate_limits` | No | Operational fixed-window limits; fleet-wide only with shared `runtime_store`. |
|
|
41
41
|
| `metrics` | No | Separately authorized scrapeable HTTP metrics. Disabled by default. |
|
|
42
42
|
| `graduated_trust` | No | Off-by-default, operator-only policy recommendation criteria and kill switch. |
|
|
43
|
+
| `generated_authority` | Generated projects | Generation-lock path and required drift enforcement for generated authority only. |
|
|
44
|
+
| `supervised_worker` | No | Default-off exact-digest allowlist for trusted automatic execution of approved proposals. |
|
|
45
|
+
| `notifications` | No | Default-off quiet attention routing to JSONL or signed HTTPS webhooks. |
|
|
43
46
|
| `executors` | No | App-owned writeback wiring. |
|
|
44
47
|
| `cloud` | Cloud mode | Scoped Cloud adapter configuration. |
|
|
45
48
|
|
|
@@ -499,6 +502,129 @@ synapsor-runner doctor --check-writeback --config ./synapsor.runner.json
|
|
|
499
502
|
The complete validation, privilege, lifecycle, and error-code contract is in
|
|
500
503
|
[Proposal And Evidence Freshness](proposal-evidence-freshness.md).
|
|
501
504
|
|
|
505
|
+
## Supervised worker
|
|
506
|
+
|
|
507
|
+
`supervised_worker` is a separate deployment opt-in for automatic execution of
|
|
508
|
+
already-approved proposals. It does not change `AUTO APPROVE`; old and new
|
|
509
|
+
auto-approved proposals still wait for manual apply unless both the contract
|
|
510
|
+
and this exact-digest allowlist permit worker execution.
|
|
511
|
+
|
|
512
|
+
```json
|
|
513
|
+
{
|
|
514
|
+
"supervised_worker": {
|
|
515
|
+
"enabled": true,
|
|
516
|
+
"profile": "production",
|
|
517
|
+
"capabilities": [
|
|
518
|
+
{
|
|
519
|
+
"capability": "billing.propose_small_credit",
|
|
520
|
+
"contract_digest": "sha256:EXACT_ACTIVE_DIGEST",
|
|
521
|
+
"mode": "supervised_worker",
|
|
522
|
+
"concurrency": 1,
|
|
523
|
+
"queue_limit": 100,
|
|
524
|
+
"lease_seconds": 60,
|
|
525
|
+
"max_attempts": 5,
|
|
526
|
+
"proposal_ttl_seconds": 3600,
|
|
527
|
+
"rate_limit": {
|
|
528
|
+
"executions": 20,
|
|
529
|
+
"window_seconds": 60
|
|
530
|
+
},
|
|
531
|
+
"write_url_env": "BILLING_POSTGRES_WRITE_URL",
|
|
532
|
+
"worker_identity": "billing_worker",
|
|
533
|
+
"control_role": "runner_operator",
|
|
534
|
+
"require_least_privilege_writer": true,
|
|
535
|
+
"writer_posture_fingerprint": "sha256:REVIEWED_POSTURE_DIGEST",
|
|
536
|
+
"required_attention_sinks": ["operations"]
|
|
537
|
+
}
|
|
538
|
+
]
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
`profile` is `development`, `staging`, or `production`. Production requires
|
|
544
|
+
verified operator identity and hardened writer posture. Every entry binds
|
|
545
|
+
capability name, active contract digest, worker mode, queue/concurrency/rate
|
|
546
|
+
bounds, proposal TTL, writer credential reference, and optional fixed worker
|
|
547
|
+
identity/control role.
|
|
548
|
+
|
|
549
|
+
`required_attention_sinks` is optional and default-off. When present, an
|
|
550
|
+
otherwise eligible job remains queued until every named enabled notification
|
|
551
|
+
sink has a healthy delivery record. Sink health never grants approval or
|
|
552
|
+
execution authority.
|
|
553
|
+
|
|
554
|
+
The active capability must itself contain
|
|
555
|
+
`execution.supervised_worker = "allowed"`, compile from `ALLOW SUPERVISED
|
|
556
|
+
WORKER APPLY`, use direct single-row guarded INSERT/UPDATE, and satisfy the
|
|
557
|
+
receipt, trusted-scope, conflict/deduplication, and eligibility checks. See
|
|
558
|
+
[Operator-Supervised Automatic Apply](supervised-automatic-apply.md).
|
|
559
|
+
|
|
560
|
+
## Notifications
|
|
561
|
+
|
|
562
|
+
`notifications` routes immutable redacted attention events. It is absent or
|
|
563
|
+
`enabled: false` by default and starts no background dispatcher.
|
|
564
|
+
|
|
565
|
+
```json
|
|
566
|
+
{
|
|
567
|
+
"notifications": {
|
|
568
|
+
"enabled": true,
|
|
569
|
+
"workbench_url_env": "SYNAPSOR_WORKBENCH_URL",
|
|
570
|
+
"sinks": [
|
|
571
|
+
{
|
|
572
|
+
"id": "operations",
|
|
573
|
+
"type": "webhook",
|
|
574
|
+
"url_env": "SYNAPSOR_NOTIFY_WEBHOOK_URL",
|
|
575
|
+
"signing_secret_env": "SYNAPSOR_NOTIFY_SIGNING_SECRET",
|
|
576
|
+
"minimum_severity": "warning",
|
|
577
|
+
"events": [
|
|
578
|
+
"proposal.review_required",
|
|
579
|
+
"worker.dead_lettered",
|
|
580
|
+
"worker.unknown_outcome"
|
|
581
|
+
],
|
|
582
|
+
"environments": ["production"],
|
|
583
|
+
"delivery": "immediate",
|
|
584
|
+
"max_attempts": 5,
|
|
585
|
+
"timeout_ms": 3000,
|
|
586
|
+
"max_response_bytes": 1024,
|
|
587
|
+
"replay_window_seconds": 300,
|
|
588
|
+
"allow_private_destinations": false,
|
|
589
|
+
"recovery_notifications": false,
|
|
590
|
+
"budgets": {
|
|
591
|
+
"per_minute": 10,
|
|
592
|
+
"per_hour": 100,
|
|
593
|
+
"cooldown_seconds": 600,
|
|
594
|
+
"retry_attempt_threshold": 3,
|
|
595
|
+
"degraded_duration_seconds": 120,
|
|
596
|
+
"queue_depth_threshold": 100,
|
|
597
|
+
"queue_age_seconds": 300
|
|
598
|
+
},
|
|
599
|
+
"quiet_hours": {
|
|
600
|
+
"start_utc_hour": 22,
|
|
601
|
+
"end_utc_hour": 7
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
]
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
Sink `type` is `webhook` or `jsonl`. JSONL requires
|
|
610
|
+
`destination: "stdout"` and accepts no URL/signing fields. Webhook URLs and
|
|
611
|
+
32-byte-or-longer signing secrets come only from the named environment
|
|
612
|
+
variables.
|
|
613
|
+
|
|
614
|
+
`delivery` is `immediate`, `digest`, or `all`. Each sink can filter fixed event
|
|
615
|
+
names, qualified capability names, environments, and minimum severity.
|
|
616
|
+
Per-minute/hour budgets, informational budgets, aggregation windows, cooldown,
|
|
617
|
+
unresolved reminders, digest cadence, escalation delay, retry/degraded
|
|
618
|
+
thresholds, queue thresholds, UTC quiet hours, and opt-in recovery messages
|
|
619
|
+
control interruption volume. Critical events bypass ordinary quiet hours and
|
|
620
|
+
budgets but still coalesce.
|
|
621
|
+
|
|
622
|
+
Private webhook destinations require both
|
|
623
|
+
`allow_private_destinations: true` and an exact
|
|
624
|
+
`private_host_allowlist`. See [Human Attention And
|
|
625
|
+
Notifications](human-attention-notifications.md) for event classes, commands,
|
|
626
|
+
signature verification, SSRF protections, and quiet-default behavior.
|
|
627
|
+
|
|
502
628
|
## Rate limits
|
|
503
629
|
|
|
504
630
|
```json
|
|
@@ -554,6 +680,12 @@ it. See [Graduated Trust Recommendations](graduated-trust.md).
|
|
|
554
680
|
|
|
555
681
|
## Operator identity
|
|
556
682
|
|
|
683
|
+
For the complete lifecycle from public DSL `APPROVAL ROLE`, through IdP group
|
|
684
|
+
mapping and JWT verification, to the immutable approval record and independent
|
|
685
|
+
apply decision, use [Approval Roles And Verified Operator
|
|
686
|
+
Identity](approval-roles-and-operator-identity.md). The examples below are the
|
|
687
|
+
configuration summary; that guide contains the tested local OIDC/JWKS flow.
|
|
688
|
+
|
|
557
689
|
Local development compatibility uses an explicitly unverified environment
|
|
558
690
|
identity:
|
|
559
691
|
|
|
@@ -287,9 +287,17 @@ Use the shared queue explicitly:
|
|
|
287
287
|
synapsor-runner proposals list --config ./synapsor.runner.json
|
|
288
288
|
synapsor-runner proposals show latest --config ./synapsor.runner.json
|
|
289
289
|
synapsor-runner ui --config ./synapsor.runner.json
|
|
290
|
-
synapsor-runner worker run --
|
|
290
|
+
synapsor-runner worker run --supervised --yes \
|
|
291
|
+
--worker-id billing_worker --config ./synapsor.runner.json
|
|
291
292
|
```
|
|
292
293
|
|
|
294
|
+
The supervised path is disabled by default and requires contract permission
|
|
295
|
+
plus a deployment allowlist for the exact digest. Existing `AUTO APPROVE`
|
|
296
|
+
contracts remain manual-apply after upgrade. Worker leases, policy reservations,
|
|
297
|
+
writer posture, source/evidence freshness, idempotency receipts, and UNKNOWN
|
|
298
|
+
outcome recovery are covered in [Operator-Supervised Automatic
|
|
299
|
+
Apply](supervised-automatic-apply.md).
|
|
300
|
+
|
|
293
301
|
Dead-letter operations require verified operator identity:
|
|
294
302
|
|
|
295
303
|
```bash
|
|
@@ -305,6 +313,13 @@ Requeue preserves history and refuses if a durable applied/already-applied
|
|
|
305
313
|
receipt already proves the effect. Discard closes the queue item without
|
|
306
314
|
deleting proposals, receipts, or events.
|
|
307
315
|
|
|
316
|
+
Use `attention show` and the Workbench Human Attention Inbox to inspect
|
|
317
|
+
review-required, stale, limit, dead-letter, UNKNOWN, and reconciliation states
|
|
318
|
+
without copying proposal IDs. Optional external notifications are disabled and
|
|
319
|
+
quiet by default. A signed generic webhook can route selected coalesced
|
|
320
|
+
incidents; it cannot approve or mutate state. See [Human Attention And
|
|
321
|
+
Notifications](human-attention-notifications.md).
|
|
322
|
+
|
|
308
323
|
## Backup, Restore, And Retention
|
|
309
324
|
|
|
310
325
|
```bash
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Reviewed Candidates From Prisma, Drizzle, And OpenAPI
|
|
2
2
|
|
|
3
|
-
Runner 1.6.
|
|
3
|
+
Runner 1.6.4 uses these same bounded static parsers as evidence in the
|
|
4
4
|
whole-application [Auto Boundary](auto-boundary-and-scoped-explore.md) flow.
|
|
5
5
|
The database catalog remains authoritative for database structure; Prisma,
|
|
6
6
|
Drizzle, OpenAPI, and existing Synapsor definitions contribute deterministic
|
|
@@ -42,6 +42,36 @@ The model does not receive:
|
|
|
42
42
|
- MCP endpoint tokens, JWTs, refresh tokens, client secrets, or TLS keys;
|
|
43
43
|
- trusted tenant or principal authority as ordinary model arguments.
|
|
44
44
|
|
|
45
|
+
## Optional Workbench model client
|
|
46
|
+
|
|
47
|
+
Workbench Ask is a local client of that same reviewed surface, not another
|
|
48
|
+
policy engine. It is available only from authenticated loopback Workbench in an
|
|
49
|
+
explicit development/staging profile. Its provider may request only the exact
|
|
50
|
+
active named capabilities plus valid local authoring tools. The request is
|
|
51
|
+
dispatched through the official MCP SDK and the existing typed-plan/runtime
|
|
52
|
+
validators; there is no parallel query or mutation executor.
|
|
53
|
+
|
|
54
|
+
The operator chooses provider, model, endpoint, and credential source and
|
|
55
|
+
acknowledges direct egress for the current authority digest. No model output can
|
|
56
|
+
change those values. Provider credentials and bounded conversation state stay
|
|
57
|
+
in local server memory and are not written to the project, ledger, browser
|
|
58
|
+
storage, logs, evidence, receipts, or replay. Reviewed tool results may be sent
|
|
59
|
+
directly to the provider after consent; Synapsor does not relay them.
|
|
60
|
+
|
|
61
|
+
Official remote providers use fixed HTTPS origins. Custom remote endpoints
|
|
62
|
+
require HTTPS; plaintext is restricted to loopback. Redirects are refused,
|
|
63
|
+
resolved destinations are checked and pinned on every connection, and
|
|
64
|
+
private/link-local/metadata/special remote addresses are blocked. Unknown
|
|
65
|
+
tools, malformed arguments/responses, authority drift, oversized data, time or
|
|
66
|
+
tool-loop exhaustion, and reported source mutation fail closed.
|
|
67
|
+
|
|
68
|
+
Provider prose remains untrusted. A write tool can create only the normal inert
|
|
69
|
+
proposal, and the Workbench directs the human to the separate operator path.
|
|
70
|
+
Ask never receives activation, Protect, approval, apply, worker, notification,
|
|
71
|
+
attention, or reconciliation tools. It is absent from production,
|
|
72
|
+
unknown-profile, shared, remote, and non-loopback surfaces. See [Workbench Ask
|
|
73
|
+
With Your Model](workbench-ask.md).
|
|
74
|
+
|
|
45
75
|
## Network transport and identity
|
|
46
76
|
|
|
47
77
|
Local stdio is the preferred one-client path. The MCP client launches Runner and
|
|
@@ -169,6 +199,20 @@ outside the model-facing MCP server and verifies:
|
|
|
169
199
|
|
|
170
200
|
If any authority check cannot be verified, the write fails closed.
|
|
171
201
|
|
|
202
|
+
Manual apply remains the default, including after policy auto-approval. An
|
|
203
|
+
eligible single-row direct INSERT or UPDATE may instead use operator-supervised
|
|
204
|
+
automatic apply, but only when the activated contract explicitly permits the
|
|
205
|
+
trusted worker and the deployment independently allowlists the exact
|
|
206
|
+
capability/digest. The model controls neither opt-in. The separately operated
|
|
207
|
+
worker leases approved work and invokes the same guarded-apply implementation
|
|
208
|
+
as manual apply; it repeats current approval, policy, limit, scope, freshness,
|
|
209
|
+
generation-lock, writer-posture, idempotency, and receipt checks immediately
|
|
210
|
+
before execution. Source or supporting-evidence drift causes zero mutation.
|
|
211
|
+
Ambiguous transaction outcomes require reconciliation and are never blindly
|
|
212
|
+
retried. Hard DELETE, reversible changes, set writes, and app-owned/external
|
|
213
|
+
effects are excluded from this first automatic path. See
|
|
214
|
+
[Operator-Supervised Automatic Apply](supervised-automatic-apply.md).
|
|
215
|
+
|
|
172
216
|
Strict freshness is intentionally limited to same-database direct SQL
|
|
173
217
|
writeback. Runner rejects app-owned or cross-source strict freshness because it
|
|
174
218
|
cannot claim one atomic transaction around those effects. Cloud can govern an
|
|
@@ -218,6 +262,16 @@ controls are not MCP tools. The UI does not expose raw SQL, database URLs, or
|
|
|
218
262
|
write credentials, and it does not let a model widen reviewed tables, columns,
|
|
219
263
|
scope, policy, or executor authority.
|
|
220
264
|
|
|
265
|
+
The authoritative store also records redacted human-attention events and
|
|
266
|
+
projects related events into a secured Workbench inbox. External delivery is
|
|
267
|
+
disabled and quiet by default. When an operator enables it, a separate
|
|
268
|
+
dispatcher can route selected coalesced incidents to a JSONL development sink
|
|
269
|
+
or signed HTTPS webhook. Delivery is at least once, redacted, budgeted, and
|
|
270
|
+
observable. A notification, webhook response, inbox acknowledgement, delivery
|
|
271
|
+
replay, or MCP update hint never activates, approves, applies, cancels,
|
|
272
|
+
recovers, or mutates anything. The ledger and Workbench remain the source of
|
|
273
|
+
truth. See [Human Attention And Notifications](human-attention-notifications.md).
|
|
274
|
+
|
|
221
275
|
Contract lint and tests are review aids rather than a proof of complete
|
|
222
276
|
security. Capability breadth can still drift as narrow tools accumulate;
|
|
223
277
|
surface-fitness lint makes high-signal generic, dense, poorly named, and
|