@synapsor/runner 1.6.1 → 1.6.3
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 +98 -3
- package/README.md +33 -19
- package/SECURITY.md +10 -2
- package/THREAT_MODEL.md +27 -4
- package/dist/authoring.mjs +53 -3
- package/dist/cli.d.ts +19 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/local-ui.d.ts +85 -1
- package/dist/local-ui.d.ts.map +1 -1
- package/dist/runner.mjs +33542 -20382
- package/dist/runtime.mjs +3581 -237
- package/dist/shadow.mjs +2423 -65
- package/docs/README.md +22 -2
- package/docs/agent-guided-setup.md +239 -0
- package/docs/approval-roles-and-operator-identity.md +353 -0
- package/docs/auto-boundary-and-scoped-explore.md +32 -6
- package/docs/capability-authoring.md +19 -0
- package/docs/current-scope.md +20 -1
- package/docs/cursor-plugin.md +2 -2
- package/docs/dsl-json-parity.md +75 -0
- package/docs/dsl-reference.md +10 -0
- package/docs/fresh-developer-usability.md +155 -80
- package/docs/getting-started-own-database.md +5 -4
- package/docs/guarded-crud-writeback.md +19 -0
- package/docs/guided-onboarding.md +331 -0
- package/docs/human-attention-notifications.md +367 -0
- package/docs/limitations.md +19 -3
- package/docs/local-mode.md +16 -2
- package/docs/migrating-to-synapsor-spec.md +38 -0
- package/docs/production.md +39 -0
- package/docs/release-notes.md +87 -4
- package/docs/runner-config-reference.md +132 -0
- package/docs/running-a-runner-fleet.md +16 -1
- package/docs/security-boundary.md +24 -0
- package/docs/store-lifecycle.md +20 -1
- package/docs/supervised-automatic-apply.md +267 -0
- package/docs/troubleshooting-first-run.md +35 -0
- package/examples/app-owned-writeback/command-handler.mjs +0 -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/mcp-postgres-billing-app-handler/scripts/run-demo.sh +0 -0
- package/examples/operator-oidc/issuer.mjs +188 -0
- package/examples/reference-support-billing-app/scripts/run-demo.sh +0 -0
- package/examples/support-billing-agent/scripts/run-demo.sh +0 -0
- package/examples/support-billing-agent/scripts/run-evaluation.sh +0 -0
- package/examples/support-plan-credit/README.md +6 -2
- package/package.json +11 -9
- package/schemas/schema-candidate-review.schema.json +42 -0
- package/schemas/synapsor.runner.schema.json +227 -4
package/docs/README.md
CHANGED
|
@@ -18,6 +18,10 @@ no-database demo, wire your database, then read deeper concepts.
|
|
|
18
18
|
you adopt Runner.
|
|
19
19
|
- [README](../README.md): audit-first proof, no-database demo, safety diagram,
|
|
20
20
|
and the shortest own-database path.
|
|
21
|
+
- [Database To First Safe Tool](guided-onboarding.md): one public command,
|
|
22
|
+
five-minute first read, ten-minute Explore to Protect, fifteen-minute
|
|
23
|
+
proposal, resumable setup, guarded development writeback, and host-neutral
|
|
24
|
+
client handoff.
|
|
21
25
|
- [Troubleshooting First Run](troubleshooting-first-run.md): common first-run
|
|
22
26
|
failures, redacted diagnostics, and fixes.
|
|
23
27
|
|
|
@@ -47,6 +51,8 @@ no-database demo, wire your database, then read deeper concepts.
|
|
|
47
51
|
|
|
48
52
|
## 04 Connect Your DB
|
|
49
53
|
|
|
54
|
+
- [Database To First Safe Tool](guided-onboarding.md): the complete
|
|
55
|
+
review-by-exception golden path using the packed npm package.
|
|
50
56
|
- [Auto Boundary, Scoped Explore, And
|
|
51
57
|
Protect](auto-boundary-and-scoped-explore.md): deterministically inspect a
|
|
52
58
|
whole staging application, review one digest-bound boundary, ask bounded row
|
|
@@ -95,6 +101,9 @@ no-database demo, wire your database, then read deeper concepts.
|
|
|
95
101
|
- [Capability Authoring](capability-authoring.md): define read/proposal
|
|
96
102
|
capabilities, model-facing descriptions, result envelopes, trusted context,
|
|
97
103
|
and writeback guards.
|
|
104
|
+
- [Approval Roles And Verified Operator Identity](approval-roles-and-operator-identity.md):
|
|
105
|
+
trace `APPROVAL ROLE` through IdP claims, immutable approval evidence, and
|
|
106
|
+
the independent apply-role decision.
|
|
98
107
|
- [DSL Reference](dsl-reference.md): complete supported grammar, clause order,
|
|
99
108
|
compiled meaning, and constraints.
|
|
100
109
|
- [Runner Config Reference](runner-config-reference.md): every public wiring
|
|
@@ -141,6 +150,14 @@ no-database demo, wire your database, then read deeper concepts.
|
|
|
141
150
|
|
|
142
151
|
- [Local Mode](local-mode.md): local store, proposals, approval, replay, and
|
|
143
152
|
writeback flow.
|
|
153
|
+
- [Operator-Supervised Automatic Apply](supervised-automatic-apply.md):
|
|
154
|
+
separate approval from execution, require contract and deployment opt-in for
|
|
155
|
+
one exact digest, run the trusted worker, and understand revalidation,
|
|
156
|
+
eligibility, retries, controls, and UNKNOWN outcomes.
|
|
157
|
+
- [Human Attention And Notifications](human-attention-notifications.md):
|
|
158
|
+
durable attention events, the no-ID Workbench inbox, quiet defaults,
|
|
159
|
+
coalescing and digests, signed generic webhooks, delivery recovery, and the
|
|
160
|
+
optional supervision-health gate.
|
|
144
161
|
- [Shadow Studies](shadow-studies.md): compare what an agent would propose
|
|
145
162
|
with explicit authorized outcomes before granting write authority.
|
|
146
163
|
- [Writeback Executors](writeback-executors.md): app-owned writeback handlers
|
|
@@ -162,8 +179,11 @@ no-database demo, wire your database, then read deeper concepts.
|
|
|
162
179
|
- [Store Lifecycle](store-lifecycle.md): active-store leases, prune safety,
|
|
163
180
|
deleted-store behavior, and concurrent server guardrails.
|
|
164
181
|
- `synapsor-runner activity search`, `evidence`, `query-audit`, `receipts`,
|
|
165
|
-
`events tail`, and `
|
|
166
|
-
|
|
182
|
+
`events tail`, `attention show`, and `notifications status`: local evidence,
|
|
183
|
+
audit, receipt, replay, complete lifecycle history, and human-attention
|
|
184
|
+
inspection. The legacy development lifecycle webhook remains documented in
|
|
185
|
+
[Local Mode](local-mode.md); production attention delivery uses the signed,
|
|
186
|
+
quiet notification pipeline.
|
|
167
187
|
- `examples/mysql-refund-agent/`: MySQL order/refund review example using the
|
|
168
188
|
same proposal, approval, guarded writeback, and replay loop.
|
|
169
189
|
- [Scoped Ledger Reports](compliance-reports.md): object/principal JSON,
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# Agent-Guided Synapsor Setup
|
|
2
|
+
|
|
3
|
+
This guide lets Claude Code, Codex, Cursor, or another coding agent help a
|
|
4
|
+
developer set up Synapsor Runner without taking over a human security decision.
|
|
5
|
+
It uses the same local-first flow a developer runs directly. No Synapsor
|
|
6
|
+
account, Cloud login, model API key, global installation, or database
|
|
7
|
+
replication is required.
|
|
8
|
+
|
|
9
|
+
## Copyable Prompt
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
Set up Synapsor Runner for this project.
|
|
13
|
+
|
|
14
|
+
Use DATABASE_URL from my environment. Run the setup yourself and explain each
|
|
15
|
+
step briefly. Stop only when I must review the data boundary or make another
|
|
16
|
+
human-authority decision. Never ask me to paste credentials into chat, and
|
|
17
|
+
never activate, approve, or apply anything for me.
|
|
18
|
+
|
|
19
|
+
Finish by driving one safe tool, showing what the agent can and cannot access,
|
|
20
|
+
and giving me the MCP configuration for my client.
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Rules For The Coding Agent
|
|
24
|
+
|
|
25
|
+
The coding agent may inspect project files, check tool availability, run the
|
|
26
|
+
documented CLI, explain failures, and resume completed work.
|
|
27
|
+
|
|
28
|
+
The coding agent must not:
|
|
29
|
+
|
|
30
|
+
- ask the user to paste a database URL, password, token, or private key into
|
|
31
|
+
chat;
|
|
32
|
+
- print or persist the value of `DATABASE_URL` or another credential;
|
|
33
|
+
- choose a tenant or principal boundary;
|
|
34
|
+
- decide that a sensitive or uncertain field is safe to expose;
|
|
35
|
+
- activate an exploration boundary or named capability;
|
|
36
|
+
- approve, reject, apply, reconcile, or revert a proposal;
|
|
37
|
+
- run writeback setup with `--apply`;
|
|
38
|
+
- weaken a failed role, RLS, schema-lock, profile, or transport check;
|
|
39
|
+
- replace an existing generated project or use `--force` without a human
|
|
40
|
+
choosing the destructive path;
|
|
41
|
+
- fetch or execute setup code other than the documented npm package command.
|
|
42
|
+
|
|
43
|
+
The model-facing MCP surface never contains activation, approval, or apply
|
|
44
|
+
tools. A coding agent operating the terminal must preserve the same separation.
|
|
45
|
+
|
|
46
|
+
## Guided Procedure
|
|
47
|
+
|
|
48
|
+
### 1. Verify Local Prerequisites
|
|
49
|
+
|
|
50
|
+
Run:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
node --version
|
|
54
|
+
test -n "${DATABASE_URL:-}"
|
|
55
|
+
npx -y @synapsor/runner@latest --version
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Do not run `echo "$DATABASE_URL"` or otherwise reveal its value. Runner
|
|
59
|
+
requires Node 22.13 or newer.
|
|
60
|
+
|
|
61
|
+
Report one sentence:
|
|
62
|
+
|
|
63
|
+
```text
|
|
64
|
+
Node, the Runner package, and an exported DATABASE_URL are available.
|
|
65
|
+
Next: inspect metadata and open the local boundary review.
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
If `DATABASE_URL` is absent, stop and say:
|
|
69
|
+
|
|
70
|
+
```text
|
|
71
|
+
DATABASE_URL is not exported in this shell. Export it locally, without pasting
|
|
72
|
+
it into chat, then ask me to continue.
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 2. Start Or Resume The Guided Project
|
|
76
|
+
|
|
77
|
+
From the intended project directory, run:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
npx -y @synapsor/runner@latest start --from-env DATABASE_URL
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
For a fresh interactive project, Runner performs metadata-only inspection,
|
|
84
|
+
creates disabled review artifacts, validates the generated project, initializes
|
|
85
|
+
the local ledger, and starts the secured loopback Workbench. It prints one
|
|
86
|
+
local URL if a browser cannot be opened.
|
|
87
|
+
|
|
88
|
+
For an existing guided project, the same command resumes it. Resume and Try do
|
|
89
|
+
not rescan the database, rewrite files, or change a digest. Do not add
|
|
90
|
+
`--force`. A schema rescan or reset is a separate human choice in Workbench.
|
|
91
|
+
|
|
92
|
+
Report:
|
|
93
|
+
|
|
94
|
+
```text
|
|
95
|
+
Runner created or resumed a disabled local boundary. No source rows were read
|
|
96
|
+
and the database was not changed.
|
|
97
|
+
Next: review the security exceptions in Workbench.
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Then stop. The human must review:
|
|
101
|
+
|
|
102
|
+
- fields kept out and fields still uncertain;
|
|
103
|
+
- exact row identity;
|
|
104
|
+
- trusted tenant and principal scope;
|
|
105
|
+
- aggregate-safe dimensions and measures;
|
|
106
|
+
- database-role and RLS posture;
|
|
107
|
+
- the exact digest being activated.
|
|
108
|
+
|
|
109
|
+
Do not click or call activation endpoints for the user.
|
|
110
|
+
|
|
111
|
+
### 3. Continue After Human Activation
|
|
112
|
+
|
|
113
|
+
After the user confirms that Workbench shows an active reviewed boundary,
|
|
114
|
+
continue in the same Workbench session. The built-in Explore form performs the
|
|
115
|
+
first bounded read and PM-style aggregate without SQL or plan JSON.
|
|
116
|
+
|
|
117
|
+
The coding agent may verify the active local surface without copying an opaque
|
|
118
|
+
handle:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
npx -y @synapsor/runner@latest try call --list --format json
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
If no named capability is active yet, that is expected: a temporary local
|
|
125
|
+
Scoped Explore boundary is not a production named tool. Ask the user to run the
|
|
126
|
+
suggested aggregate in Workbench and select **Protect this analysis**.
|
|
127
|
+
|
|
128
|
+
Protect creates public DSL, canonical JSON, tests, and a disabled named
|
|
129
|
+
capability. Stop again for exact-digest human activation. Do not activate it
|
|
130
|
+
for the user.
|
|
131
|
+
|
|
132
|
+
### 4. Verify The First Named Tool
|
|
133
|
+
|
|
134
|
+
After the human activates a protected named capability, run:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
npx -y @synapsor/runner@latest try call --list --format json
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Use the tool name returned by that command. For a tool with generated sample
|
|
141
|
+
input, run:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
npx -y @synapsor/runner@latest try call <tool-name> --sample --json
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Do not invent tenant or principal arguments. Trusted scope comes from the
|
|
148
|
+
reviewed environment/session binding outside model input.
|
|
149
|
+
|
|
150
|
+
Report:
|
|
151
|
+
|
|
152
|
+
```text
|
|
153
|
+
Your first safe named tool is working.
|
|
154
|
+
The result used only reviewed fields and trusted scope.
|
|
155
|
+
Source database changed: no.
|
|
156
|
+
Next: connect the same reviewed tool surface to your MCP client.
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### 5. Present MCP Setup
|
|
160
|
+
|
|
161
|
+
Workbench displays ready-to-copy configurations for:
|
|
162
|
+
|
|
163
|
+
- Cursor project MCP;
|
|
164
|
+
- Claude-compatible local stdio MCP;
|
|
165
|
+
- Codex;
|
|
166
|
+
- generic stdio MCP.
|
|
167
|
+
|
|
168
|
+
All hosts launch the same reviewed Runner project. None receives a broader
|
|
169
|
+
authority surface.
|
|
170
|
+
|
|
171
|
+
For a terminal-rendered generic configuration, run:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
npx -y @synapsor/runner@latest mcp config --absolute-paths
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Do not write into a client configuration without explicit user consent. Cursor
|
|
178
|
+
has a separate consent-gated installer:
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
npx -y @synapsor/runner@latest mcp install cursor --project --dry-run
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Show the dry-run result first. Installation remains a human choice.
|
|
185
|
+
|
|
186
|
+
## Guided Write Action
|
|
187
|
+
|
|
188
|
+
After the first safe read, Workbench can generate one bounded write action.
|
|
189
|
+
Schema structure supplies candidates only. The human decides business intent,
|
|
190
|
+
allowed fields, values or transitions, trusted scope, version guard, reviewer,
|
|
191
|
+
limits, optional bounded policy approval, receipt mode, and compensation.
|
|
192
|
+
|
|
193
|
+
The generated action starts disabled. Its first model-facing call creates an
|
|
194
|
+
exact proposal and must say:
|
|
195
|
+
|
|
196
|
+
```text
|
|
197
|
+
Proposal created.
|
|
198
|
+
Source database changed: no.
|
|
199
|
+
The model cannot approve or apply this proposal.
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Stop at action activation, proposal approval, writeback setup, apply, and
|
|
203
|
+
revert. Those are human or trusted-operator decisions outside MCP.
|
|
204
|
+
|
|
205
|
+
Manual apply is the default. An operator may separately configure
|
|
206
|
+
digest-bound supervised execution for eligible production actions. In that
|
|
207
|
+
mode, the response must disclose that reviewed policy may approve and a trusted
|
|
208
|
+
worker may later apply the proposal without a per-request click. The coding
|
|
209
|
+
agent still cannot enable, start, pause, configure, or control that worker. See
|
|
210
|
+
[Operator-Supervised Automatic Apply](supervised-automatic-apply.md).
|
|
211
|
+
|
|
212
|
+
## Recovery
|
|
213
|
+
|
|
214
|
+
Use the one next action Runner prints. Preserve the current project and review
|
|
215
|
+
state.
|
|
216
|
+
|
|
217
|
+
| Failure | Required response |
|
|
218
|
+
| --- | --- |
|
|
219
|
+
| Connection failed | Confirm the local environment variable and network path without printing the credential, then rerun `start`. |
|
|
220
|
+
| Privileged, writable, owner, `BYPASSRLS`, or unverifiable read role | Stop source-row exploration. Ask the user to supply a verifiably read-only role. |
|
|
221
|
+
| Tenant, principal, or row identity unresolved | Stop and direct the human to the highlighted Workbench exception. |
|
|
222
|
+
| Sensitive field blocked | Keep it out unless a human records a reasoned field decision. |
|
|
223
|
+
| Existing generated project | Run the same `start` command and choose Resume. Do not use `--force`. |
|
|
224
|
+
| Stale generation lock | Preserve active named capabilities; choose Rescan and review changes. |
|
|
225
|
+
| Workbench port unavailable | Use the single alternate loopback URL Runner prints or free the occupied local port. |
|
|
226
|
+
| Writeback prerequisites missing | Keep the proposal unchanged. Use `writeback setup` for a preview only and stop for human review. |
|
|
227
|
+
|
|
228
|
+
## What The Developer Should Understand
|
|
229
|
+
|
|
230
|
+
At the end, ask the developer to explain these four facts:
|
|
231
|
+
|
|
232
|
+
1. The model never receives raw SQL authority.
|
|
233
|
+
2. Reads are limited to reviewed fields under trusted tenant and principal
|
|
234
|
+
scope.
|
|
235
|
+
3. Model-facing writes create proposals rather than committing changes.
|
|
236
|
+
4. The model cannot activate authority, approve proposals, or apply writes.
|
|
237
|
+
|
|
238
|
+
If any answer is unclear, reopen the Workbench Overview. Do not expand authority
|
|
239
|
+
to make onboarding appear successful.
|
|
@@ -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.
|