@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,353 @@
|
|
|
1
|
+
# Approval Roles And Verified Operator Identity
|
|
2
|
+
|
|
3
|
+
This guide explains how a role written in a Synapsor capability becomes a
|
|
4
|
+
verified human approval. The short version is:
|
|
5
|
+
|
|
6
|
+
```text
|
|
7
|
+
Contract says who may approve
|
|
8
|
+
-> identity provider says who the operator is and which groups they hold
|
|
9
|
+
-> Runner verifies a fresh token and exact role
|
|
10
|
+
-> ledger records the exact decision without storing the bearer token
|
|
11
|
+
-> a separate verified operator authorizes apply
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Approval and apply are outside the model-facing MCP tool surface. An agent
|
|
15
|
+
cannot supply an operator token, select its own role, approve a proposal, or
|
|
16
|
+
commit a write.
|
|
17
|
+
|
|
18
|
+
## What `APPROVAL ROLE` Means
|
|
19
|
+
|
|
20
|
+
This public DSL:
|
|
21
|
+
|
|
22
|
+
```sql
|
|
23
|
+
CREATE CAPABILITY membership.freeze_membership
|
|
24
|
+
...
|
|
25
|
+
APPROVAL ROLE membership_reviewer
|
|
26
|
+
...
|
|
27
|
+
;
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
means that an operator approving this capability's proposal must have the exact
|
|
31
|
+
role string `membership_reviewer` in a verified identity.
|
|
32
|
+
|
|
33
|
+
`membership_reviewer` is an author-chosen Synapsor contract role. It is not:
|
|
34
|
+
|
|
35
|
+
- a PostgreSQL role;
|
|
36
|
+
- an operating-system user or group;
|
|
37
|
+
- an MCP or model role;
|
|
38
|
+
- an API key or secret;
|
|
39
|
+
- permission to apply the write.
|
|
40
|
+
|
|
41
|
+
The contract fixes the required role. The model cannot put a different role in
|
|
42
|
+
a tool argument.
|
|
43
|
+
|
|
44
|
+
## The Complete Identity Chain
|
|
45
|
+
|
|
46
|
+
1. A contract author writes `APPROVAL ROLE membership_reviewer`.
|
|
47
|
+
2. An identity administrator maps real people to an IdP group. For example,
|
|
48
|
+
members of `example-membership-reviewers` receive
|
|
49
|
+
`membership_reviewer` in the token's `groups` claim.
|
|
50
|
+
3. Runner's `operator_identity` configuration fixes the token source, allowed
|
|
51
|
+
algorithm, public-key source, issuer, audience, subject claim, roles claim,
|
|
52
|
+
and apply roles.
|
|
53
|
+
4. For every approval, Runner reads a fresh token from environment, a protected
|
|
54
|
+
token file, or stdin.
|
|
55
|
+
5. Runner verifies the asymmetric algorithm, signature and JWKS key, issuer,
|
|
56
|
+
audience, `exp`, and `nbf`. It then reads a safe subject and the configured
|
|
57
|
+
roles claim.
|
|
58
|
+
6. Runner requires an exact role match. `membership_reviewer_backup` does not
|
|
59
|
+
satisfy `membership_reviewer`.
|
|
60
|
+
7. The approval is bound to the immutable proposal ID, version, and hash, the
|
|
61
|
+
action `approve`, the verified subject, and the decision time.
|
|
62
|
+
8. Runner stores the verified identity metadata and an attested decision proof.
|
|
63
|
+
It never stores the bearer token.
|
|
64
|
+
9. Apply is a second operator decision. A fresh verified identity must satisfy
|
|
65
|
+
`operator_identity.apply_roles`; approval does not imply commit authority.
|
|
66
|
+
|
|
67
|
+
At apply time Runner also verifies the stored approval proof against the exact
|
|
68
|
+
proposal. A changed proposal ID, version, hash, role, subject, decision, or
|
|
69
|
+
attestation fails before source mutation.
|
|
70
|
+
|
|
71
|
+
## Development-Only Identity
|
|
72
|
+
|
|
73
|
+
For one-user local development, `dev_env` preserves the lightweight workflow:
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
{
|
|
77
|
+
"operator_identity": {
|
|
78
|
+
"provider": "dev_env",
|
|
79
|
+
"actor_env": "SYNAPSOR_OPERATOR_ID",
|
|
80
|
+
"roles_env": "SYNAPSOR_OPERATOR_ROLES",
|
|
81
|
+
"apply_roles": ["writeback_operator"]
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
export SYNAPSOR_OPERATOR_ID="local-reviewer"
|
|
88
|
+
export SYNAPSOR_OPERATOR_ROLES="membership_reviewer,writeback_operator"
|
|
89
|
+
|
|
90
|
+
synapsor-runner proposals approve latest \
|
|
91
|
+
--config ./synapsor.runner.json \
|
|
92
|
+
--store ./.synapsor/local.db \
|
|
93
|
+
--yes
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`dev_env` is explicitly unverified. Environment text is trusted as entered. Do
|
|
97
|
+
not use it for a shared Runner, production, or a decision that must prove who
|
|
98
|
+
approved it. Use `jwt_oidc` or `signed_key` there.
|
|
99
|
+
|
|
100
|
+
## Production-Style OIDC Configuration
|
|
101
|
+
|
|
102
|
+
The following block is parsed and used by the packed OIDC integration test.
|
|
103
|
+
The simulated issuer uses the same shape, but this is not Okta, Entra, Auth0,
|
|
104
|
+
or any other vendor certification.
|
|
105
|
+
|
|
106
|
+
<!-- synapsor-oidc-operator-config:start -->
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"provider": "jwt_oidc",
|
|
110
|
+
"apply_roles": ["writeback_operator"],
|
|
111
|
+
"token_env": "SYNAPSOR_OPERATOR_TOKEN",
|
|
112
|
+
"roles_claim": "groups",
|
|
113
|
+
"subject_claim": "sub",
|
|
114
|
+
"attestation_secret_env": "SYNAPSOR_OPERATOR_ATTESTATION_SECRET",
|
|
115
|
+
"algorithms": ["RS256"],
|
|
116
|
+
"jwks_url_env": "SYNAPSOR_OPERATOR_JWKS_URL",
|
|
117
|
+
"issuer": "https://identity.example.test/oidc",
|
|
118
|
+
"audience": "synapsor-operators",
|
|
119
|
+
"clock_skew_seconds": 0,
|
|
120
|
+
"jwks_cache_seconds": 60,
|
|
121
|
+
"jwks_cooldown_seconds": 1,
|
|
122
|
+
"fetch_timeout_ms": 2000,
|
|
123
|
+
"max_response_bytes": 8192
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
<!-- synapsor-oidc-operator-config:end -->
|
|
127
|
+
|
|
128
|
+
Place that object at `operator_identity` in `synapsor.runner.json`. The
|
|
129
|
+
configuration uses environment-variable names, not their secret values.
|
|
130
|
+
|
|
131
|
+
The IdP mapping for this example is:
|
|
132
|
+
|
|
133
|
+
| IdP group | JWT `groups` value | Synapsor purpose |
|
|
134
|
+
| --- | --- | --- |
|
|
135
|
+
| `example-membership-reviewers` | `membership_reviewer` | Satisfies the capability's exact `APPROVAL ROLE`. |
|
|
136
|
+
| `example-writeback-operators` | `writeback_operator` | Satisfies the independent `apply_roles` gate. |
|
|
137
|
+
|
|
138
|
+
Configure this mapping in the IdP. Runner does not infer it from database roles
|
|
139
|
+
or group names.
|
|
140
|
+
|
|
141
|
+
### What Each Setting Does
|
|
142
|
+
|
|
143
|
+
| Setting | Meaning |
|
|
144
|
+
| --- | --- |
|
|
145
|
+
| `algorithms` | Exact asymmetric algorithms Runner accepts. OIDC operator identity supports `RS256` and `ES256`; do not infer this from the token header. |
|
|
146
|
+
| `jwks_url_env` | Environment variable containing the IdP's public JWKS URL. Remote URLs must use HTTPS; HTTP is accepted only on loopback for development. |
|
|
147
|
+
| `issuer` / `audience` | Values Runner requires in `iss` and `aud`. |
|
|
148
|
+
| `subject_claim` | Verified top-level claim used as the operator identity. |
|
|
149
|
+
| `roles_claim` | Verified top-level claim containing exact Synapsor role strings. |
|
|
150
|
+
| `token_env` | Environment variable holding the short-lived operator bearer token for this command. |
|
|
151
|
+
| `attestation_secret_env` | Environment variable holding at least 32 bytes of Runner-side key material used to attest the redacted stored decision. |
|
|
152
|
+
| `apply_roles` | Roles accepted for the separate apply decision. |
|
|
153
|
+
|
|
154
|
+
The attestation secret is not the OIDC token and not an IdP client secret.
|
|
155
|
+
Runner uses it locally to make the stored, token-free identity proof
|
|
156
|
+
tamper-evident. Every trusted process that verifies the same ledger needs the
|
|
157
|
+
same protected value. Keep it out of source control, MCP configuration, logs,
|
|
158
|
+
and model context.
|
|
159
|
+
|
|
160
|
+
## Run The Local OIDC/JWKS Fixture
|
|
161
|
+
|
|
162
|
+
The npm package includes a synthetic localhost-only issuer. It generates
|
|
163
|
+
ephemeral RS256 private keys in memory, serves only public keys at `/jwks`, and
|
|
164
|
+
mints fixed test identities. It must never be used as a real identity provider.
|
|
165
|
+
|
|
166
|
+
In terminal 1:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
export SYNAPSOR_EXAMPLE_OIDC_PORT=9411
|
|
170
|
+
node ./node_modules/@synapsor/runner/examples/operator-oidc/issuer.mjs
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
It prints one JSON object describing:
|
|
174
|
+
|
|
175
|
+
```text
|
|
176
|
+
JWKS: http://127.0.0.1:9411/jwks
|
|
177
|
+
Issuer: https://identity.example.test/oidc
|
|
178
|
+
Audience: synapsor-operators
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
The server holds the private keys only in memory. Stop it after the walkthrough.
|
|
182
|
+
|
|
183
|
+
In terminal 2, from a Synapsor project that has one pending
|
|
184
|
+
`membership_reviewer` proposal:
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
export SYNAPSOR_OPERATOR_JWKS_URL="http://127.0.0.1:9411/jwks"
|
|
188
|
+
export SYNAPSOR_OPERATOR_ATTESTATION_SECRET="$(openssl rand -base64 32)"
|
|
189
|
+
|
|
190
|
+
export SYNAPSOR_OPERATOR_TOKEN="$(
|
|
191
|
+
curl -fsS http://127.0.0.1:9411/token/reviewer |
|
|
192
|
+
node -pe 'JSON.parse(require("node:fs").readFileSync(0,"utf8")).access_token'
|
|
193
|
+
)"
|
|
194
|
+
|
|
195
|
+
synapsor-runner proposals approve latest \
|
|
196
|
+
--config ./synapsor.runner.json \
|
|
197
|
+
--store ./.synapsor/local.db \
|
|
198
|
+
--yes \
|
|
199
|
+
--json
|
|
200
|
+
|
|
201
|
+
unset SYNAPSOR_OPERATOR_TOKEN
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
The token is supplied through the environment and is not a command-line
|
|
205
|
+
argument. For a real deployment, inject a short-lived token through a protected
|
|
206
|
+
process environment, protected token file, or stdin. Never commit it.
|
|
207
|
+
|
|
208
|
+
The fixture's apply identity uses a rotated key. Keep the old and new public
|
|
209
|
+
keys overlapped during rotation:
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
curl -fsS -X POST http://127.0.0.1:9411/rotate
|
|
213
|
+
|
|
214
|
+
export SYNAPSOR_OPERATOR_TOKEN="$(
|
|
215
|
+
curl -fsS http://127.0.0.1:9411/token/applier |
|
|
216
|
+
node -pe 'JSON.parse(require("node:fs").readFileSync(0,"utf8")).access_token'
|
|
217
|
+
)"
|
|
218
|
+
|
|
219
|
+
synapsor-runner apply latest \
|
|
220
|
+
--config ./synapsor.runner.json \
|
|
221
|
+
--store ./.synapsor/local.db \
|
|
222
|
+
--json
|
|
223
|
+
|
|
224
|
+
unset SYNAPSOR_OPERATOR_TOKEN
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
This second token has `writeback_operator`, not
|
|
228
|
+
`membership_reviewer`. It authorizes apply only after Runner verifies the
|
|
229
|
+
stored approval and all guarded-write prerequisites.
|
|
230
|
+
|
|
231
|
+
### Token File Or Stdin
|
|
232
|
+
|
|
233
|
+
Configure exactly one token source.
|
|
234
|
+
|
|
235
|
+
For a protected token file, replace `token_env` with:
|
|
236
|
+
|
|
237
|
+
```json
|
|
238
|
+
{
|
|
239
|
+
"token_file_env": "SYNAPSOR_OPERATOR_TOKEN_FILE"
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Set the environment variable to a mode-`0600` file outside the repository.
|
|
244
|
+
Delete the file after the decision.
|
|
245
|
+
|
|
246
|
+
For stdin, replace `token_env` with:
|
|
247
|
+
|
|
248
|
+
```json
|
|
249
|
+
{
|
|
250
|
+
"token_stdin": true
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Use `--yes` so confirmation does not compete for stdin:
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
printf '%s\n' "$SHORT_LIVED_OPERATOR_TOKEN" |
|
|
258
|
+
synapsor-runner proposals approve "$PROPOSAL_ID" \
|
|
259
|
+
--config ./synapsor.runner.json \
|
|
260
|
+
--store ./.synapsor/local.db \
|
|
261
|
+
--yes
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Do not pass a token as a CLI argument.
|
|
265
|
+
|
|
266
|
+
## What The Ledger Stores
|
|
267
|
+
|
|
268
|
+
Inspect the full lifecycle without copying an opaque ID:
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
synapsor-runner replay show latest \
|
|
272
|
+
--config ./synapsor.runner.json \
|
|
273
|
+
--store ./.synapsor/local.db \
|
|
274
|
+
--json
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
The approval record has this shape:
|
|
278
|
+
|
|
279
|
+
```json
|
|
280
|
+
{
|
|
281
|
+
"proposal_version": 1,
|
|
282
|
+
"proposal_hash": "sha256:...",
|
|
283
|
+
"approver": "reviewer@example.test",
|
|
284
|
+
"status": "approved",
|
|
285
|
+
"identity": {
|
|
286
|
+
"provider": "jwt_oidc",
|
|
287
|
+
"verified": true,
|
|
288
|
+
"subject": "reviewer@example.test",
|
|
289
|
+
"roles": ["membership_reviewer"],
|
|
290
|
+
"key_id": "fitflow-key-1",
|
|
291
|
+
"algorithm": "RS256",
|
|
292
|
+
"issuer": "https://identity.example.test/oidc",
|
|
293
|
+
"decision": {
|
|
294
|
+
"schema_version": "synapsor.operator-decision.v1",
|
|
295
|
+
"action": "approve",
|
|
296
|
+
"proposal_id": "wrp_...",
|
|
297
|
+
"proposal_version": 1,
|
|
298
|
+
"proposal_hash": "sha256:...",
|
|
299
|
+
"subject": "reviewer@example.test",
|
|
300
|
+
"issued_at": "..."
|
|
301
|
+
},
|
|
302
|
+
"decision_hash": "sha256:...",
|
|
303
|
+
"signature": "...",
|
|
304
|
+
"integrity_hash": "sha256:..."
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
`signature` is Runner's HMAC attestation over the redacted verified identity
|
|
310
|
+
and exact decision. It is not the original OIDC JWT signature. The bearer token
|
|
311
|
+
is absent.
|
|
312
|
+
|
|
313
|
+
The replay also contains a `writeback_authorized` event for the independently
|
|
314
|
+
verified apply identity and the normal guarded-write receipt.
|
|
315
|
+
|
|
316
|
+
## JWKS Caching And Key Rotation
|
|
317
|
+
|
|
318
|
+
For a long-running Runner process, remote JWKS keys are held in memory for
|
|
319
|
+
`jwks_cache_seconds` (default 600 seconds). An unknown `kid` may trigger a
|
|
320
|
+
bounded refresh after `jwks_cooldown_seconds` (default 30 seconds). Fetches have
|
|
321
|
+
a timeout and maximum response size, reject redirects, and accept public keys
|
|
322
|
+
only. A one-shot CLI command starts with an empty in-memory cache.
|
|
323
|
+
|
|
324
|
+
During rotation:
|
|
325
|
+
|
|
326
|
+
1. Publish the new public key while retaining the old key.
|
|
327
|
+
2. Wait at least the configured cache lifetime plus expected clock skew.
|
|
328
|
+
3. Start issuing tokens with the new `kid`.
|
|
329
|
+
4. Keep the overlap long enough for long-running processes to refresh.
|
|
330
|
+
5. Remove the old key only after old tokens and caches have expired.
|
|
331
|
+
|
|
332
|
+
Runner's verifier tests exercise an in-process unknown-`kid` refresh. The packed
|
|
333
|
+
approval integration uses the old key for approval, publishes an overlapping
|
|
334
|
+
new key, and uses the new key for the independent apply decision.
|
|
335
|
+
|
|
336
|
+
## Fail-Closed Troubleshooting
|
|
337
|
+
|
|
338
|
+
| Failure | What Runner does | Remediation |
|
|
339
|
+
| --- | --- | --- |
|
|
340
|
+
| Missing or similar role | Refuses the decision; proposal stays pending. | Map the exact contract role into the configured roles claim. Do not rename the role at runtime. |
|
|
341
|
+
| Wrong issuer or audience | Rejects JWT verification. | Match the exact IdP issuer and application audience. |
|
|
342
|
+
| Unknown `kid` | Refreshes only within the bounded JWKS policy, then rejects if no public key matches. | Publish overlapping keys and wait for the cooldown/cache policy. |
|
|
343
|
+
| Invalid signature | Rejects the token. | Obtain a fresh token from the configured issuer; do not bypass verification. |
|
|
344
|
+
| Expired or not-yet-valid token | Rejects the token. | Fix clock synchronization and obtain a currently valid token. Keep clock skew narrow. |
|
|
345
|
+
| Unsafe subject or malformed roles | Refuses identity resolution. | Emit a safe top-level string subject and an array or delimited string of safe role names. |
|
|
346
|
+
| JWKS endpoint unavailable | Fails closed when a required key cannot be resolved. | Restore HTTPS/network/DNS service or use a reviewed public-key source. Do not fall back to `dev_env`. |
|
|
347
|
+
| Missing or short attestation secret | Refuses to record the decision. | Supply at least 32 bytes of protected Runner-side key material. |
|
|
348
|
+
| Stored identity proof changed | Apply fails before source mutation. | Investigate ledger integrity. Create a new proposal and approval; do not edit the proof. |
|
|
349
|
+
| Approver lacks an apply role | Apply is refused even after approval. | Use a separately verified operator whose token has one configured `apply_roles` value. |
|
|
350
|
+
|
|
351
|
+
Database availability, database roles, and MCP arguments never substitute for a
|
|
352
|
+
verified operator identity. Keep database least privilege and RLS beneath this
|
|
353
|
+
operator decision boundary.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Auto Boundary, Scoped Explore, And Protect
|
|
2
2
|
|
|
3
|
-
Runner 1.6.
|
|
3
|
+
Runner 1.6.4 provides a deterministic, resumable authoring path for a real
|
|
4
|
+
application:
|
|
4
5
|
|
|
5
6
|
```text
|
|
6
7
|
Connect staging
|
|
@@ -25,7 +26,14 @@ controls underneath Runner. PostgreSQL deployments should use forced row-level
|
|
|
25
26
|
security (RLS) where possible; MySQL deployments should use restricted views or
|
|
26
27
|
tenant-bound credentials.
|
|
27
28
|
|
|
28
|
-
|
|
29
|
+
The shortest first run accepts a hidden URL paste or asks before reading
|
|
30
|
+
`DATABASE_URL` from a regular project `.env`:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npx -y @synapsor/runner start
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
For an explicit environment-driven staging run:
|
|
29
37
|
|
|
30
38
|
```bash
|
|
31
39
|
export DATABASE_URL='postgresql://runner_reader:REPLACE_ME@127.0.0.1:5432/app'
|
|
@@ -108,13 +116,19 @@ candidates start disabled. Auto Boundary never replaces an active contract.
|
|
|
108
116
|
No source rows, credentials, tenant values, or principal values are written to
|
|
109
117
|
these files.
|
|
110
118
|
|
|
119
|
+
Workbench previews generated DSL with a local deterministic highlighter. It
|
|
120
|
+
distinguishes DSL keywords from reviewer-defined names, strings, numbers,
|
|
121
|
+
comments, and punctuation while preserving byte-identical plain/copy text.
|
|
122
|
+
Rendering uses escaped DOM text and no external CDN; if highlighting fails, the
|
|
123
|
+
same safe plain source remains visible.
|
|
124
|
+
|
|
111
125
|
## Review The Boundary
|
|
112
126
|
|
|
113
127
|
The Workbench requires a human to narrow and confirm:
|
|
114
128
|
|
|
115
129
|
- development or staging deployment profile;
|
|
116
130
|
- trusted tenant and principal bindings supplied outside model arguments;
|
|
117
|
-
- included resources and
|
|
131
|
+
- included resources and catalog-proven relationship paths;
|
|
118
132
|
- selectable fields;
|
|
119
133
|
- filterable fields and allowed operators;
|
|
120
134
|
- sortable and groupable fields;
|
|
@@ -146,6 +160,52 @@ The immutable digest covers the reviewed resources, field permissions,
|
|
|
146
160
|
relationships, scope, role posture, generation lock, compiler/spec version,
|
|
147
161
|
profile, and every query/privacy budget. Model arguments cannot widen it.
|
|
148
162
|
|
|
163
|
+
## Try Without An External MCP Host
|
|
164
|
+
|
|
165
|
+
Workbench can run the first safe read, bounded aggregate, and Protect flow
|
|
166
|
+
directly. The same authority is available through CLI Try:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
synapsor-runner try call --list --format json
|
|
170
|
+
synapsor-runner try explore --suggested --json
|
|
171
|
+
synapsor-runner try protect --name analytics.protected_analysis --json
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Cursor, Claude, Codex, and generic stdio are optional clients, not onboarding
|
|
175
|
+
dependencies. The packed FitFlow gate proves Workbench, CLI Try, and an
|
|
176
|
+
official-SDK generic stdio client produce the same bounded result and denial
|
|
177
|
+
behavior.
|
|
178
|
+
|
|
179
|
+
## Optional Workbench Ask Client
|
|
180
|
+
|
|
181
|
+
After the no-model composer succeeds, Workbench can optionally send a
|
|
182
|
+
plain-language question to a developer-selected model provider. This adds a
|
|
183
|
+
client, not authority:
|
|
184
|
+
|
|
185
|
+
```text
|
|
186
|
+
provider request
|
|
187
|
+
-> exact active Workbench tool registry
|
|
188
|
+
-> official MCP SDK
|
|
189
|
+
-> existing Scoped Explore/runtime validator
|
|
190
|
+
-> reviewed bounded result or refusal
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
While Explore is active, the provider sees only `app.describe_data` and
|
|
194
|
+
`app.explore_data`. After Protect and Explore shutdown, it sees only activated
|
|
195
|
+
named capabilities. It never receives Protect, activation, approval, apply,
|
|
196
|
+
worker, notification, recovery, credential, or filesystem tools.
|
|
197
|
+
|
|
198
|
+
Provider/model/origin and the exact authority digest require explicit
|
|
199
|
+
direct-egress acknowledgement. Keys come from the local Runner process
|
|
200
|
+
environment or a session-only masked paste; configuration and history remain
|
|
201
|
+
in memory and are cleared on request or shutdown. Ask is restricted to secured
|
|
202
|
+
loopback Workbench in development/staging and is absent from production and
|
|
203
|
+
shared/remote surfaces.
|
|
204
|
+
|
|
205
|
+
The no-model composer, CLI Try, and external MCP routes remain fully supported
|
|
206
|
+
when no provider is configured or a provider is unavailable. See [Workbench
|
|
207
|
+
Ask With Your Model](workbench-ask.md).
|
|
208
|
+
|
|
149
209
|
## Add The Authoring Tools To Cursor
|
|
150
210
|
|
|
151
211
|
After activation, let Runner manage only its own project entry:
|
|
@@ -202,8 +262,9 @@ analytics database tool. It supports:
|
|
|
202
262
|
- bounded top-N results;
|
|
203
263
|
- at most two reviewed time ranges;
|
|
204
264
|
- one resource by default;
|
|
205
|
-
-
|
|
206
|
-
|
|
265
|
+
- up to three activated relationship paths in one plan;
|
|
266
|
+
- one or two inspected, reviewed many-to-one foreign-key links per path, each
|
|
267
|
+
with maximum fan-out one.
|
|
207
268
|
|
|
208
269
|
It does not support arbitrary `DISTINCT`, `HAVING`, formulas, window functions,
|
|
209
270
|
unions, nested queries, many-to-many joins, system catalogs, user-defined
|
|
@@ -211,6 +272,14 @@ functions, or a general join planner. Scope is enforced independently on every
|
|
|
211
272
|
participating relation. Runner refuses a plan when cardinality, fan-out,
|
|
212
273
|
counted entity, or scope cannot be proven.
|
|
213
274
|
|
|
275
|
+
Auto Boundary does not activate every discovered path. When a useful question
|
|
276
|
+
needs one inactive but catalog-proven path, Runner refuses the plan and stages
|
|
277
|
+
that exact path for operator review. Workbench shows the foreign-key proof and,
|
|
278
|
+
for optional links, requires an explicit choice between excluding the unmatched
|
|
279
|
+
counted row and keeping it with an empty group value. Activating the new exact
|
|
280
|
+
digest is an operator-plane action; the model cannot perform it. See
|
|
281
|
+
[Reviewed Relationship Paths](reviewed-relationships.md).
|
|
282
|
+
|
|
214
283
|
Before returning groups, Runner enforces the reviewed minimum cohort size.
|
|
215
284
|
Small groups are suppressed and revealing totals are withheld. Durable
|
|
216
285
|
per-session extraction and differencing budgets block repeated slightly
|
|
@@ -263,7 +332,7 @@ handles.
|
|
|
263
332
|
|
|
264
333
|
Choose the useful query in Workbench. Protect freezes:
|
|
265
334
|
|
|
266
|
-
- resources and reviewed relationship
|
|
335
|
+
- resources and reviewed relationship paths;
|
|
267
336
|
- counted entity, measures, dimensions, and bucket structure;
|
|
268
337
|
- filters, ordering, top-N, and comparison shape;
|
|
269
338
|
- tenant/principal as trusted bindings;
|
|
@@ -323,16 +392,25 @@ their previous startup, `doctor`, contract, and tool behavior.
|
|
|
323
392
|
|
|
324
393
|
## Verify The Reference Journey
|
|
325
394
|
|
|
326
|
-
The
|
|
395
|
+
The polished packed-artifact journey is under
|
|
396
|
+
`examples/fitflow-guided-onboarding`. The smaller
|
|
397
|
+
`examples/auto-boundary-churn` fixture remains a compatibility and focused
|
|
398
|
+
aggregate-security gate.
|
|
327
399
|
|
|
328
400
|
From a source checkout:
|
|
329
401
|
|
|
330
402
|
```bash
|
|
331
403
|
corepack pnpm test:auto-boundary-explore
|
|
332
404
|
corepack pnpm test:auto-boundary-explore:packed
|
|
405
|
+
corepack pnpm test:guided-onboarding:packed
|
|
333
406
|
```
|
|
334
407
|
|
|
335
|
-
The packed
|
|
336
|
-
|
|
337
|
-
production Explore absence,
|
|
338
|
-
|
|
408
|
+
The packed gates prove the PostgreSQL + Next.js + Prisma + Workbench flow,
|
|
409
|
+
host-neutral CLI/generic-stdio parity, all aggregate
|
|
410
|
+
denial/suppression/budget checks, Protect, production Explore absence,
|
|
411
|
+
protected-capability survival, guided proposal/apply/compensation, verified
|
|
412
|
+
OIDC roles, durable redacted audit, and unchanged source data before every
|
|
413
|
+
authorized mutation.
|
|
414
|
+
|
|
415
|
+
For the timed first-read, Explore, Protect, proposal, and writeback walkthrough,
|
|
416
|
+
use [Database To First Safe Tool](guided-onboarding.md).
|
|
@@ -118,6 +118,24 @@ CREATE CAPABILITY billing.propose_late_fee_waiver
|
|
|
118
118
|
END
|
|
119
119
|
```
|
|
120
120
|
|
|
121
|
+
`billing_lead` is a Synapsor contract role, not a database role or secret. For
|
|
122
|
+
shared or production operation, Runner must resolve that exact role from a
|
|
123
|
+
freshly verified operator identity and separately authorize apply. See
|
|
124
|
+
[Approval Roles And Verified Operator
|
|
125
|
+
Identity](approval-roles-and-operator-identity.md).
|
|
126
|
+
|
|
127
|
+
Manual apply remains the default. For an eligible direct single-row
|
|
128
|
+
INSERT/UPDATE, a reviewer may additionally place this clause before `END`:
|
|
129
|
+
|
|
130
|
+
```sql
|
|
131
|
+
ALLOW SUPERVISED WORKER APPLY
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
It grants only contract-side permission and becomes part of the exact contract
|
|
135
|
+
digest. Automatic execution still requires an independent deployment allowlist
|
|
136
|
+
for that capability and digest; see [Operator-Supervised Automatic
|
|
137
|
+
Apply](supervised-automatic-apply.md).
|
|
138
|
+
|
|
121
139
|
Compile and validate:
|
|
122
140
|
|
|
123
141
|
```bash
|
|
@@ -229,6 +247,7 @@ reviewed runner JSON capabilities. Current parity:
|
|
|
229
247
|
| proposal weak `conflict_guard` | `CONFLICT GUARD WEAK ROW HASH ACKNOWLEDGED` | 1.4.4 | Explicit legacy escape hatch for an ordinary single-row source-DB UPDATE only. It hashes the captured projection and may miss concurrent changes outside that projection. It is rejected for DELETE, reversible writes, bounded sets, and Runner-ledger authority. |
|
|
230
248
|
| proposal `approval` | `APPROVAL ROLE billing_lead` | 0.1 | Local mode records the required role; enforcement is still outside the model-facing MCP tool. |
|
|
231
249
|
| proposal `approval.required_approvals` | `REQUIRE 2 APPROVALS` | 1.1 | Optional 1..10 distinct-reviewer quorum; defaults to 1. |
|
|
250
|
+
| proposal `execution.supervised_worker` | `ALLOW SUPERVISED WORKER APPLY` | 1.6 | Optional contract-side permission for eligible trusted-worker apply. It does nothing without a separate exact-digest deployment allowlist; old auto-approved proposals remain manual-apply. |
|
|
232
251
|
| proposal `writeback` | `WRITEBACK DIRECT SQL`, `WRITEBACK APP HANDLER EXECUTOR name`, `WRITEBACK CLOUD WORKER`, `WRITEBACK NONE` | 0.1.7 | Handler URLs/tokens stay in `synapsor.runner.json`; contracts carry only the handler name. |
|
|
233
252
|
| proposal `reversibility` | `REVERSIBLE` | 1.4 | Direct SQL only. Captures a bounded inverse after unambiguous apply; operator `revert` creates a new independently approved proposal. |
|
|
234
253
|
| evidence options | `REQUIRE EVIDENCE` | 0.1 | Detailed evidence sources/handle prefixes are not expressible in DSL yet; use embedded JSON or generated contract JSON for those. |
|
|
@@ -387,8 +406,8 @@ surface.
|
|
|
387
406
|
### Optional Live Freshness
|
|
388
407
|
|
|
389
408
|
The proposal's exact `conflict_guard` already protects its target at apply.
|
|
390
|
-
Runner
|
|
391
|
-
|
|
409
|
+
Runner can additionally recheck that target before local approval and bind
|
|
410
|
+
explicitly reviewed supporting rows to both approval and apply.
|
|
392
411
|
|
|
393
412
|
This is deployment wiring, not DSL or canonical contract syntax:
|
|
394
413
|
|
package/docs/client-recipes.md
CHANGED
|
@@ -33,7 +33,7 @@ Run from the Runner repository root after supplying your own environment-bound
|
|
|
33
33
|
staging credentials and trusted scope:
|
|
34
34
|
|
|
35
35
|
```bash
|
|
36
|
-
npx -y
|
|
36
|
+
npx -y @synapsor/runner tools preview \
|
|
37
37
|
--config ./examples/support-plan-credit/synapsor.runner.json \
|
|
38
38
|
--store ./tmp/support-plan-credit/local.db
|
|
39
39
|
```
|
package/docs/cloud-mode.md
CHANGED
|
@@ -88,17 +88,17 @@ cp .env.example .env
|
|
|
88
88
|
# Fill the placeholders in .env, including the one-time Runner token.
|
|
89
89
|
set -a && . ./.env && set +a
|
|
90
90
|
|
|
91
|
-
npx -y
|
|
92
|
-
npx -y
|
|
93
|
-
npx -y
|
|
94
|
-
npx -y
|
|
91
|
+
npx -y @synapsor/runner config validate --config ./synapsor.runner.json
|
|
92
|
+
npx -y @synapsor/runner tools preview --config ./synapsor.runner.json --store ./.synapsor/local.db
|
|
93
|
+
npx -y @synapsor/runner cloud connect --config ./synapsor.cloud.json
|
|
94
|
+
npx -y @synapsor/runner mcp serve --config ./synapsor.runner.json --store ./.synapsor/local.db
|
|
95
95
|
```
|
|
96
96
|
|
|
97
97
|
Run the trusted worker in a second operator-controlled terminal:
|
|
98
98
|
|
|
99
99
|
```bash
|
|
100
100
|
set -a && . ./.env && set +a
|
|
101
|
-
npx -y
|
|
101
|
+
npx -y @synapsor/runner runner start --config ./synapsor.runner.json --store ./.synapsor/local.db
|
|
102
102
|
```
|
|
103
103
|
|
|
104
104
|
For deployment checks or an operator-controlled single claim cycle, use the
|
|
@@ -107,13 +107,13 @@ Cloud-approved job and still rechecks the local contract, proposal, tenant,
|
|
|
107
107
|
version, bounds, and idempotency guards:
|
|
108
108
|
|
|
109
109
|
```bash
|
|
110
|
-
npx -y
|
|
110
|
+
npx -y @synapsor/runner runner start --once --config ./synapsor.runner.json --store ./.synapsor/local.db
|
|
111
111
|
```
|
|
112
112
|
|
|
113
113
|
When a local proposal exists:
|
|
114
114
|
|
|
115
115
|
```bash
|
|
116
|
-
npx -y
|
|
116
|
+
npx -y @synapsor/runner cloud sync latest --config ./synapsor.cloud.json --store ./.synapsor/local.db
|
|
117
117
|
```
|
|
118
118
|
|
|
119
119
|
See [Cloud Push](cloud-push.md) and [Runner Bundles](runner-bundles.md).
|
package/docs/conformance.md
CHANGED
|
@@ -64,9 +64,12 @@ bound to a required trusted principal and AND-composed with tenant scope. Run
|
|
|
64
64
|
same-tenant denial and shared-ledger evidence-handle isolation.
|
|
65
65
|
The protected-read parity fixture proves digest- and generation-lock-bound
|
|
66
66
|
named reads, fixed predicates, reviewed measures/dimensions/time buckets,
|
|
67
|
-
one-hop many-to-one limits, suppression,
|
|
68
|
-
differencing budgets, and rejection of raw SQL or
|
|
69
|
-
scope. `corepack pnpm test:
|
|
67
|
+
catalog-proven one-hop/star/depth-two many-to-one limits, suppression,
|
|
68
|
+
response/query/extraction/differencing budgets, and rejection of raw SQL or
|
|
69
|
+
model-controlled trusted scope. `corepack pnpm test:reviewed-relationships`
|
|
70
|
+
proves direct and depth-two paths, per-relation scope, nullable semantics,
|
|
71
|
+
demand-driven activation, and fan-out refusal on live PostgreSQL/MySQL.
|
|
72
|
+
`corepack pnpm test:auto-boundary-explore:packed` proves the complete
|
|
70
73
|
authoring-to-production behavior through packed public artifacts.
|
|
71
74
|
|
|
72
75
|
Additional 0.1 parity coverage currently lives in tests and verification
|