@synapsor/runner 1.4.123 → 1.5.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.
Files changed (98) hide show
  1. package/CHANGELOG.md +144 -19
  2. package/README.md +132 -135
  3. package/dist/authoring.d.ts +23 -0
  4. package/dist/authoring.d.ts.map +1 -0
  5. package/dist/authoring.mjs +1318 -0
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/cli.js +4 -5
  8. package/dist/local-ui.d.ts +14 -0
  9. package/dist/local-ui.d.ts.map +1 -1
  10. package/dist/runner.mjs +18427 -7084
  11. package/dist/runtime.d.ts +50 -0
  12. package/dist/runtime.d.ts.map +1 -0
  13. package/dist/runtime.mjs +15094 -0
  14. package/dist/shadow.d.ts +36 -0
  15. package/dist/shadow.d.ts.map +1 -0
  16. package/dist/shadow.mjs +5262 -0
  17. package/docs/README.md +44 -5
  18. package/docs/alternatives.md +122 -0
  19. package/docs/capability-authoring.md +44 -1
  20. package/docs/client-recipes.md +218 -0
  21. package/docs/contract-testing.md +9 -7
  22. package/docs/cursor-plugin.md +78 -0
  23. package/docs/database-enforced-scope.md +273 -0
  24. package/docs/dependency-license-inventory.md +6 -2
  25. package/docs/doctor.md +17 -0
  26. package/docs/effect-regression.md +179 -0
  27. package/docs/fresh-developer-usability.md +110 -0
  28. package/docs/getting-started-own-database.md +103 -2
  29. package/docs/host-compatibility.md +59 -0
  30. package/docs/local-mode.md +12 -7
  31. package/docs/mcp-apps.md +188 -0
  32. package/docs/mcp-audit.md +220 -2
  33. package/docs/mcp-client-setup.md +20 -4
  34. package/docs/mcp-clients.md +41 -4
  35. package/docs/oss-vs-cloud.md +3 -0
  36. package/docs/production.md +8 -0
  37. package/docs/release-notes.md +138 -14
  38. package/docs/release-policy.md +9 -5
  39. package/docs/runner-config-reference.md +25 -0
  40. package/docs/schema-api-candidates.md +68 -0
  41. package/docs/security-boundary.md +30 -5
  42. package/docs/shadow-studies.md +241 -0
  43. package/docs/troubleshooting-first-run.md +46 -0
  44. package/examples/mcp-postgres-billing-app-handler/app-handler.mjs +9 -1
  45. package/examples/mcp-postgres-billing-app-handler/schema.sql +11 -0
  46. package/examples/mcp-postgres-billing-app-handler/synapsor-handler.mjs +3 -0
  47. package/examples/raw-sql-vs-synapsor/Makefile +2 -2
  48. package/examples/raw-sql-vs-synapsor/README.md +6 -4
  49. package/examples/support-billing-agent/Makefile +5 -1
  50. package/examples/support-billing-agent/README.md +57 -5
  51. package/examples/support-billing-agent/app/README.md +6 -0
  52. package/examples/support-billing-agent/app/contract.ts +28 -0
  53. package/examples/support-billing-agent/app/effect-adapter.mjs +23 -0
  54. package/examples/support-billing-agent/app/record-shadow-outcomes.mjs +44 -0
  55. package/examples/support-billing-agent/db/schema.sql +88 -1
  56. package/examples/support-billing-agent/db/seed.sql +15 -15
  57. package/examples/support-billing-agent/scripts/run-demo.sh +4 -0
  58. package/examples/support-billing-agent/scripts/run-evaluation.sh +63 -0
  59. package/examples/support-billing-agent/shadow-study/cases.jsonl +6 -0
  60. package/examples/support-billing-agent/shadow-study/outcomes.jsonl +2 -0
  61. package/examples/support-billing-agent/synapsor.runner.json +17 -5
  62. package/examples/support-plan-credit/README.md +48 -2
  63. package/examples/support-plan-credit/mcp-client-examples/README.md +23 -0
  64. package/examples/support-plan-credit/mcp-client-examples/claude-code.sh +34 -0
  65. package/examples/support-plan-credit/mcp-client-examples/codex.config.toml +24 -0
  66. package/examples/support-plan-credit/mcp-client-examples/generic-stdio.mjs +35 -0
  67. package/examples/support-plan-credit/mcp-client-examples/generic-streamable-http.mjs +31 -0
  68. package/examples/support-plan-credit/mcp-client-examples/google-adk.py +40 -0
  69. package/examples/support-plan-credit/mcp-client-examples/langchain.mjs +29 -0
  70. package/examples/support-plan-credit/mcp-client-examples/llamaindex.py +36 -0
  71. package/examples/support-plan-credit/mcp-client-examples/openai-agents-stdio.ts +1 -1
  72. package/examples/support-plan-credit/mcp-client-examples/openai-agents-streamable-http.ts +1 -1
  73. package/examples/support-plan-credit/mcp-client-examples/vscode.mcp.json +20 -0
  74. package/examples/support-plan-credit/synapsor/actions/support.propose_plan_credit.contract-tests.generated.json +221 -0
  75. package/examples/support-plan-credit/synapsor/actions/support.propose_plan_credit.ts +34 -0
  76. package/fixtures/effects/changed/eff_support_late_fee_v1.result.json +58 -0
  77. package/fixtures/effects/dataset.json +8 -0
  78. package/fixtures/effects/results/eff_support_late_fee_v1.result.json +59 -0
  79. package/fixtures/effects/support-late-fee.effect.json +71 -0
  80. package/fixtures/generators/drizzle/malicious.ts +11 -0
  81. package/fixtures/generators/drizzle/schema.ts +12 -0
  82. package/fixtures/generators/openapi/external-ref.yaml +15 -0
  83. package/fixtures/generators/openapi/openapi.yaml +81 -0
  84. package/fixtures/generators/prisma/schema.prisma +34 -0
  85. package/fixtures/mcp-audit/README.md +14 -0
  86. package/fixtures/mcp-audit/cursor-bypass-config.json +38 -0
  87. package/fixtures/mcp-audit/dangerous-tools-list.json +33 -0
  88. package/fixtures/mcp-audit/reviewed-proposal-tools-list.json +60 -0
  89. package/package.json +29 -3
  90. package/schemas/effect-dataset.schema.json +19 -0
  91. package/schemas/effect-fixture.schema.json +191 -0
  92. package/schemas/effect-result.schema.json +52 -0
  93. package/schemas/mcp-audit-candidates.schema.json +41 -0
  94. package/schemas/mcp-audit-report.schema.json +203 -0
  95. package/schemas/schema-candidate-review.schema.json +167 -0
  96. package/schemas/schema-candidates.schema.json +49 -0
  97. package/schemas/synapsor.contract-tests.schema.json +1 -1
  98. package/schemas/synapsor.runner.schema.json +51 -0
@@ -11,8 +11,12 @@ npx -y -p @synapsor/runner synapsor-runner demo --quick
11
11
  npm install -g @synapsor/runner
12
12
  ```
13
13
 
14
- Use `@alpha` or an exact prerelease only when intentionally testing the moving
15
- preview channel.
14
+ Use an exact prerelease or a named preview tag only when intentionally testing
15
+ preview behavior. Check the tags that currently exist before documenting one:
16
+
17
+ ```bash
18
+ npm dist-tag ls @synapsor/runner
19
+ ```
16
20
 
17
21
  ## Alpha Expectations
18
22
 
@@ -128,9 +132,9 @@ internals. Local store migrations may happen inside `1.x` and later minor
128
132
  versions, but documented CLI inspection commands should remain the supported way
129
133
  to read the store.
130
134
 
131
- Alpha users should pin an exact alpha version in package.json, CI, and MCP
132
- client snippets. Use `@alpha` only when intentionally testing the moving
133
- preview channel.
135
+ Prerelease users should pin an exact version in package.json, CI, and MCP
136
+ client snippets. Do not assume an `alpha`, `beta`, or `next` tag exists; inspect
137
+ the registry tags first.
134
138
 
135
139
  ## Result Envelope Migration
136
140
 
@@ -114,6 +114,14 @@ not print database URLs or create the schema.
114
114
  "write_url_env": "BILLING_POSTGRES_WRITE_URL",
115
115
  "read_only": false,
116
116
  "statement_timeout_ms": 3000,
117
+ "database_scope": {
118
+ "mode": "postgres_rls",
119
+ "tenant_setting": "synapsor.tenant_id",
120
+ "principal_setting": "synapsor.principal"
121
+ },
122
+ "credential_scope": {
123
+ "mode": "shared"
124
+ },
117
125
  "receipts": {
118
126
  "authority": "source_db",
119
127
  "provisioning": "precreated",
@@ -138,6 +146,23 @@ adapter-specific reviewed SSL options when used.
138
146
 
139
147
  A contract's `SOURCE billing_postgres` must exactly match a `sources` key.
140
148
 
149
+ `database_scope.mode` defaults to `application`: Runner enforces trusted
150
+ tenant/principal predicates with the configured database credential.
151
+ `postgres_rls` is PostgreSQL-only and requires fixed, distinct
152
+ `tenant_setting` and `principal_setting` names. Runner binds them
153
+ transaction-locally and refuses serving/writeback when RLS, FORCE, role, or
154
+ operation-policy prerequisites cannot be attested.
155
+
156
+ `credential_scope.mode` defaults to `shared`. `tenant_resolver` names a
157
+ programmatic `TenantCredentialResolver`; credentials remain outside the
158
+ contract/config and pools are partitioned by trusted scope and credential
159
+ identity. The stock CLI does not load executable resolver modules, so CLI-only
160
+ deployments should run one tenant-bound process/credential per tenant.
161
+
162
+ See [Database-Enforced Tenant And Principal
163
+ Scope](database-enforced-scope.md) for the guarantee matrix, PostgreSQL policy
164
+ example, resolver interface, doctor canary, and MySQL alternatives.
165
+
141
166
  Long-running servers reuse native driver pools. Optional `pool` keys are
142
167
  `max_connections` (default 10), `connection_timeout_ms` (3000),
143
168
  `idle_timeout_ms` (30000), `queue_timeout_ms` (5000), and `queue_limit`
@@ -0,0 +1,68 @@
1
+ # Reviewed Candidates From Prisma, Drizzle, And OpenAPI
2
+
3
+ Use these generators after schema or API inspection when you want a review
4
+ starting point without granting authority:
5
+
6
+ ```bash
7
+ synapsor-runner init from-prisma ./prisma/schema.prisma \
8
+ --output ./synapsor-prisma-candidates
9
+
10
+ synapsor-runner init from-drizzle ./src/schema.ts \
11
+ --output ./synapsor-drizzle-candidates
12
+
13
+ synapsor-runner init from-openapi ./openapi.yaml \
14
+ --output ./synapsor-openapi-candidates
15
+ ```
16
+
17
+ Each command writes a separate deterministic directory containing:
18
+
19
+ - `synapsor.candidate.contract.json`: canonical `@synapsor/spec` candidates;
20
+ - `synapsor.candidate.runner.json`: source-less strict Shadow configuration;
21
+ - `synapsor.candidate.contract-tests.json`: deny, redaction, and operator
22
+ boundary test candidates;
23
+ - `generation-review.json`: stable machine-readable review findings;
24
+ - `REVIEW.md`: the human review checklist;
25
+ - `.synapsor-schema-candidates.json`: Runner's ownership marker.
26
+
27
+ No candidate is active. Proposal writeback is `none`, no database source is
28
+ configured, and tenant/principal authority remains a visible placeholder.
29
+ Generation never edits the input or an active Runner configuration.
30
+
31
+ ## What Is And Is Not Inferred
32
+
33
+ The generators can suggest structural facts and likely review targets:
34
+
35
+ - objects, fields, table mappings, primary keys, and version-like fields;
36
+ - potential tenant/principal fields based on names;
37
+ - potentially sensitive and kept-out fields;
38
+ - possible read and proposal capability names;
39
+ - operations that need business logic or an app-owned handler.
40
+
41
+ They do not decide which tenant or principal is authoritative, which fields are
42
+ safe to expose, which writes are valid, business bounds, approval policy, or
43
+ auto-approval. A field-name heuristic is not data classification.
44
+
45
+ Review `generation-review.json` and `REVIEW.md`, replace every
46
+ `review_required_*` placeholder, run the generated tests and a
47
+ [Shadow study](shadow-studies.md), then deliberately copy reviewed definitions
48
+ into an active contract through code review.
49
+
50
+ ## Input Safety
51
+
52
+ Input is bounded to 2 MiB, 50 objects, 128 fields per object, and 200 generated
53
+ capabilities. Deep or oversized structures fail closed.
54
+
55
+ - Prisma uses Runner's bounded lexer/parser. Datasource URLs, defaults, enum
56
+ values, generators, and plugins are not copied or run.
57
+ - Drizzle is untrusted executable TypeScript. Runner parses a documented static
58
+ `pgTable`/`mysqlTable` object-literal subset with the TypeScript AST. It never
59
+ imports, transpiles, type-checks, executes, or resolves the application.
60
+ Dynamic table names fail; unsupported dynamic columns are called out for
61
+ review.
62
+ - OpenAPI 3 JSON/YAML is parsed locally. Network/external references are
63
+ rejected. Server URLs, examples, defaults, enum values, callbacks, webhooks,
64
+ and credentials are not copied.
65
+
66
+ Existing output is never replaced unless `--force` points to a directory with
67
+ Runner's matching ownership marker. Hand-edited directories are refused even
68
+ with `--force`.
@@ -36,6 +36,7 @@ The model does not receive:
36
36
  - a generic `execute_sql` tool;
37
37
  - arbitrary table, schema, or column names;
38
38
  - database URLs or credentials;
39
+ - contract activation tools;
39
40
  - approval tools;
40
41
  - commit/writeback tools;
41
42
  - trusted tenant or principal authority as ordinary model arguments.
@@ -50,6 +51,16 @@ Trusted context comes from local configuration, environment bindings, or Cloud
50
51
  session context in Cloud mode. Tenant, principal, and authorization scope must
51
52
  not be accepted from the model as authority.
52
53
 
54
+ With a shared credential, these checks are application-level enforcement and
55
+ depend on Runner's fixed predicate implementation. Optional PostgreSQL RLS mode
56
+ adds an independent database check for omitted predicates and pooled-context
57
+ leakage. Tenant-bound credentials or isolated deployments provide the stronger
58
+ boundary when a Runner process must not hold organization-wide authority.
59
+ PostgreSQL RLS does not protect against a fully compromised process that can
60
+ choose arbitrary trusted settings, and MySQL has no native RLS equivalent. See
61
+ [Database-Enforced Tenant And Principal
62
+ Scope](database-enforced-scope.md).
63
+
53
64
  A capability may declare `principal_scope_key` (DSL: `PRINCIPAL SCOPE KEY`) to
54
65
  narrow rows inside a tenant. Runner then adds both fixed, parameterized guards:
55
66
 
@@ -74,6 +85,18 @@ explicit host-level administrative boundary.
74
85
  Proposal tools read the current row through the read credential, store evidence
75
86
  and an exact before/after diff, and leave the source database unchanged.
76
87
 
88
+ Code-first Safe Actions do not create authority when a file is edited. Runner
89
+ parses the restricted TypeScript object without importing or executing it,
90
+ compiles it into a digest-addressed disabled canonical draft, and keeps the
91
+ active contract pointer unchanged. Activation is available only in the
92
+ session-token/CSRF-protected localhost Workbench after a matching real staging
93
+ proposal proves `source_database_changed:false`; the operator must confirm the
94
+ complete digest. A proposal remains pinned to the active contract digest it was
95
+ created under, so later activation cannot reinterpret an old approval. Cloud-
96
+ linked projects route activation through Cloud governance instead of the local
97
+ control. No model-facing MCP tool can validate-and-activate, approve, apply, or
98
+ revert.
99
+
77
100
  The local proposal store rejects obvious credential material before persistence:
78
101
  database URLs, bearer tokens, Synapsor runner tokens, private-key blocks, and
79
102
  secret-like fields such as password, token, API key, private key, cookie,
@@ -135,11 +158,13 @@ database identifiers such as `../private`, `id/../../tenant_id`, or
135
158
  `status; DROP TABLE tickets` before adapter execution. Local CLI file paths
136
159
  remain explicit user-provided paths; they are not model-facing authority.
137
160
 
138
- Local review can happen through the CLI or `synapsor-runner ui`. The UI is a localhost
139
- review surface with a per-run session token and CSRF protection for
140
- approve/reject actions. It does not expose raw SQL, database URLs, write
141
- credentials, approval tools, commit tools, or controls that widen reviewed
142
- tables/columns.
161
+ Local review can happen through the CLI or `synapsor-runner ui`. The UI is a
162
+ localhost review surface with a per-run session token and CSRF protection for
163
+ approve/reject and Safe Action draft controls. Activation additionally requires
164
+ a matching source-unchanged preview plus the complete digest. These operator
165
+ controls are not MCP tools. The UI does not expose raw SQL, database URLs, or
166
+ write credentials, and it does not let a model widen reviewed tables, columns,
167
+ scope, policy, or executor authority.
143
168
 
144
169
  Contract lint and tests are review aids rather than a proof of complete
145
170
  security. Capability breadth can still drift as narrow tools accumulate;
@@ -0,0 +1,241 @@
1
+ # Shadow Studies
2
+
3
+ Use a shadow study before granting an agent write authority. Runner serves the
4
+ reviewed proposal capability and records what the agent would have proposed,
5
+ but the proposal cannot be approved, queued, or applied.
6
+
7
+ A study compares two explicit records:
8
+
9
+ ```text
10
+ agent result or proposed effect
11
+ versus
12
+ authoritative outcome recorded by your application or operator
13
+ ```
14
+
15
+ Runner does not infer a human rejection from an unchanged database row. A case
16
+ without an authoritative outcome remains visibly unmatched.
17
+
18
+ ## Safety Boundary
19
+
20
+ Set the Runner configuration to:
21
+
22
+ ```json
23
+ {
24
+ "version": 1,
25
+ "mode": "shadow"
26
+ }
27
+ ```
28
+
29
+ The proposal store enforces the shadow boundary below the CLI and local UI:
30
+
31
+ - a shadow proposal cannot be approved;
32
+ - it cannot become a writeback job or worker item;
33
+ - it cannot invoke direct database writeback or an app-owned handler;
34
+ - no flag bypasses these checks;
35
+ - a report suggestion is inactive and never edits or activates a contract.
36
+
37
+ Shadow data remains in the configured local Runner store. Case and outcome
38
+ imports reject obvious credentials, bearer tokens, database URLs, and
39
+ secret-like fields.
40
+
41
+ ## Start A Study
42
+
43
+ Create a study before serving the shadow capability. An empty capability list
44
+ means all shadow capabilities; repeat `--capability` or use comma-separated
45
+ names to narrow it.
46
+
47
+ ```bash
48
+ synapsor-runner shadow study create \
49
+ --id sst_support_pilot \
50
+ --name "Support waiver pilot" \
51
+ --capability billing.propose_late_fee_waiver \
52
+ --store ./.synapsor/local.db
53
+
54
+ synapsor-runner mcp serve \
55
+ --config ./synapsor.runner.json \
56
+ --store ./.synapsor/local.db
57
+ ```
58
+
59
+ New matching shadow proposals are attached automatically. To attach proposals
60
+ that already exist, or to repair correlation after an interrupted process:
61
+
62
+ ```bash
63
+ synapsor-runner shadow study sync sst_support_pilot \
64
+ --store ./.synapsor/local.db
65
+ ```
66
+
67
+ ## Record Non-Proposal Results
68
+
69
+ A study also needs tasks where the agent was unable or not allowed to propose.
70
+ Import one JSON object with `case record`, or a JSON array/JSONL file with
71
+ `case import`.
72
+
73
+ ```json
74
+ {
75
+ "request_id": "req-policy-42",
76
+ "tenant_id": "acme",
77
+ "principal": "support-agent-shadow",
78
+ "capability": "billing.propose_late_fee_waiver",
79
+ "business_object": "invoice",
80
+ "object_id": "INV-3042",
81
+ "agent_result": "policy_denied",
82
+ "decision_reason": "amount above reviewed bound",
83
+ "risk_score": 35
84
+ }
85
+ ```
86
+
87
+ Allowed `agent_result` values are:
88
+
89
+ - `proposed`
90
+ - `policy_denied`
91
+ - `unable_to_propose`
92
+ - `stale_conflict`
93
+ - `invalid_unsafe_scope_attempt`
94
+
95
+ For `proposed`, include normalized `before`, `after`, and `patch` objects under
96
+ `proposed_effect`.
97
+
98
+ ```bash
99
+ synapsor-runner shadow case import \
100
+ --study sst_support_pilot \
101
+ --input ./cases.jsonl \
102
+ --store ./.synapsor/local.db
103
+ ```
104
+
105
+ Imports are limited to 2 MiB and 10,000 records per command.
106
+
107
+ ## Record Authoritative Outcomes
108
+
109
+ Export actual outcomes from your application audit log or have a trusted
110
+ operator create them. Each outcome must match an existing case by study,
111
+ request, trusted tenant, business object, and object ID. A supplied proposal ID
112
+ must match too.
113
+
114
+ ```json
115
+ {
116
+ "request_id": "request_123",
117
+ "proposal_id": "wrp_123",
118
+ "tenant_id": "acme",
119
+ "business_object": "invoice",
120
+ "object_id": "INV-3001",
121
+ "actor": "support_lead_1",
122
+ "disposition": "applied",
123
+ "actual_effect": {
124
+ "before": { "late_fee_cents": 5500 },
125
+ "after": { "late_fee_cents": 0 },
126
+ "patch": { "late_fee_cents": 0 }
127
+ },
128
+ "occurred_at": "2026-07-19T12:00:00.000Z",
129
+ "source": "support-system-audit",
130
+ "reference": "ticket:SUP-184",
131
+ "reason": "customer qualified"
132
+ }
133
+ ```
134
+
135
+ Allowed dispositions are `applied`, `rejected_no_action`, and
136
+ `stale_conflict`. An applied outcome requires an exact normalized effect.
137
+
138
+ ```bash
139
+ synapsor-runner shadow outcome import \
140
+ --study sst_support_pilot \
141
+ --input ./outcomes.jsonl \
142
+ --store ./.synapsor/local.db
143
+ ```
144
+
145
+ For the normal application path, record the trusted outcome directly instead
146
+ of assembling JSONL. `@synapsor/runner/shadow` writes through the same scope,
147
+ effect-shape, and secret checks as the import command:
148
+
149
+ ```js
150
+ import { createShadowOutcomeRecorder } from "@synapsor/runner/shadow";
151
+
152
+ const recorder = createShadowOutcomeRecorder({
153
+ storePath: "./.synapsor/local.db",
154
+ studyId: "sst_support_pilot",
155
+ actor: "support_application",
156
+ source: "support-audit-log",
157
+ });
158
+
159
+ try {
160
+ recorder.record({
161
+ requestId: "request_123",
162
+ tenantId: "acme",
163
+ businessObject: "invoice",
164
+ objectId: "INV-3001",
165
+ disposition: "applied",
166
+ actualEffect: {
167
+ before: { late_fee_cents: 5500 },
168
+ after: { late_fee_cents: 0 },
169
+ patch: { late_fee_cents: 0 },
170
+ },
171
+ });
172
+ } finally {
173
+ recorder.close();
174
+ }
175
+ ```
176
+
177
+ The application owns the truth of this outcome. The helper does not read or
178
+ mutate the source database and cannot approve a shadow proposal.
179
+
180
+ ## Review The Report
181
+
182
+ ```bash
183
+ synapsor-runner shadow report \
184
+ --study sst_support_pilot \
185
+ --output ./shadow-report.json \
186
+ --store ./.synapsor/local.db
187
+
188
+ synapsor-runner ui \
189
+ --config ./synapsor.runner.json \
190
+ --store ./.synapsor/local.db
191
+ ```
192
+
193
+ The stable JSON report preserves total and comparable denominators and
194
+ classifies exact, partial, disagreement, human rejection/no action, policy
195
+ denial, unable-to-propose, stale/conflict, unmatched, and unsafe-scope cases.
196
+ It also includes amount distributions, capability/reason breakdowns, and
197
+ risk-ranked disagreements.
198
+
199
+ The report also shows the explicit trust progression:
200
+
201
+ ```text
202
+ Observe -> Compare -> Manual review -> Suggested bounded policy
203
+ ```
204
+
205
+ It reports an insufficient sample size until the minimum comparable evidence
206
+ exists. A suggested policy remains an inactive review artifact; the report
207
+ never widens or activates the contract.
208
+
209
+ Close a completed study:
210
+
211
+ ```bash
212
+ synapsor-runner shadow study close sst_support_pilot \
213
+ --store ./.synapsor/local.db
214
+ ```
215
+
216
+ ## Deterministic Reference Data
217
+
218
+ The packaged `examples/support-billing-agent/shadow-study/` directory contains
219
+ six synthetic cases and two authoritative outcomes. It covers an exact $55
220
+ waiver, human rejection, policy denial, stale state, tenant forgery, and an
221
+ unmatched task.
222
+
223
+ ```bash
224
+ synapsor-runner shadow study create \
225
+ --id sst_support_reference \
226
+ --name "Support reference study" \
227
+ --capability billing.propose_late_fee_waiver \
228
+ --store ./.synapsor/shadow-reference.db
229
+
230
+ synapsor-runner shadow case import \
231
+ --study sst_support_reference \
232
+ --input ./examples/support-billing-agent/shadow-study/cases.jsonl \
233
+ --store ./.synapsor/shadow-reference.db
234
+
235
+ node ./examples/support-billing-agent/app/record-shadow-outcomes.mjs \
236
+ ./.synapsor/shadow-reference.db sst_support_reference
237
+
238
+ synapsor-runner shadow report \
239
+ --study sst_support_reference \
240
+ --store ./.synapsor/shadow-reference.db
241
+ ```
@@ -12,6 +12,52 @@ Use JSON for automation:
12
12
  npx -y -p @synapsor/runner synapsor-runner doctor --first-run --json
13
13
  ```
14
14
 
15
+ ## Safe Action Draft Does Not Appear As A Tool
16
+
17
+ This is expected before activation. `start --action`, agent edits, `action
18
+ validate`, and `action watch` can create or refresh only a disabled draft. They
19
+ must not alter the active model-facing tools.
20
+
21
+ Check status without exposing credentials:
22
+
23
+ ```bash
24
+ synapsor-runner action status --json
25
+ synapsor-runner tools preview \
26
+ --config ./synapsor.runner.json \
27
+ --store ./.synapsor/local.db
28
+ ```
29
+
30
+ Open the secured Workbench, run the real source-unchanged staging Data PR
31
+ preview, review the complete digest, and activate it there. There is
32
+ intentionally no activation CLI command to hand to a coding agent.
33
+
34
+ ## Activated Tool Does Not Appear In Cursor
35
+
36
+ Some MCP hosts do not refresh `tools/list` for a running stdio session. First
37
+ confirm that Runner's active tool surface changed:
38
+
39
+ ```bash
40
+ synapsor-runner action status --json
41
+ synapsor-runner mcp status cursor --project --check-launch
42
+ ```
43
+
44
+ Then reconnect or restart the project MCP server as directed by the Workbench.
45
+ Do not work around a stale host session by adding approval, apply, activation,
46
+ credentials, tenant values, or raw SQL to the Cursor configuration.
47
+
48
+ ## Safe Action Validation Reports Review Placeholders
49
+
50
+ The composer fails closed while any `__REVIEW_*__` authority placeholder or
51
+ dynamic TypeScript expression remains. Review the reported field and source,
52
+ then make the authority explicit in the restricted `defineCapability({...})`
53
+ object. Runner will not infer trusted tenant/principal bindings, hidden fields,
54
+ write columns, bounds, conflict guards, approval, or executor authority from
55
+ application code.
56
+
57
+ Use the generated explanation and test manifest after validation. Do not edit
58
+ digest-addressed files under `.synapsor/drafts/` or `.synapsor/active/`; edit
59
+ the TypeScript source and validate again.
60
+
15
61
  ## Smoke Proposal Missing From Another Runner
16
62
 
17
63
  What happened:
@@ -119,5 +119,13 @@ function writeJson(response, statusCode, body) {
119
119
  }
120
120
 
121
121
  async function loadHandlerHelper() {
122
- return await import(new URL("./synapsor-handler.mjs", import.meta.url));
122
+ const bundledHelper = new URL("./synapsor-handler.mjs", import.meta.url);
123
+ try {
124
+ return await import(bundledHelper);
125
+ } catch (error) {
126
+ if (error?.code !== "ERR_MODULE_NOT_FOUND" || error?.url !== bundledHelper.href) {
127
+ throw error;
128
+ }
129
+ return await import(new URL("../../packages/handler/dist/index.js", import.meta.url));
130
+ }
123
131
  }
@@ -50,6 +50,16 @@ CREATE TABLE IF NOT EXISTS public.synapsor_writeback_receipts (
50
50
  completed_at timestamptz
51
51
  );
52
52
 
53
+ CREATE TABLE IF NOT EXISTS public.synapsor_handler_receipts (
54
+ idempotency_key text PRIMARY KEY,
55
+ proposal_id text NOT NULL,
56
+ action text NOT NULL,
57
+ status text NOT NULL,
58
+ receipt_json jsonb NOT NULL,
59
+ created_at timestamptz NOT NULL DEFAULT now(),
60
+ completed_at timestamptz
61
+ );
62
+
53
63
  DO $$
54
64
  BEGIN
55
65
  IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'synapsor_reader') THEN
@@ -65,5 +75,6 @@ GRANT CONNECT ON DATABASE synapsor_billing_app_handler TO synapsor_reader, synap
65
75
  GRANT USAGE ON SCHEMA public TO synapsor_reader, synapsor_writer;
66
76
  GRANT SELECT ON public.tenants, public.customers, public.invoices, public.account_credits TO synapsor_reader, synapsor_writer;
67
77
  GRANT SELECT, INSERT, UPDATE ON public.synapsor_writeback_receipts TO synapsor_writer;
78
+ GRANT SELECT, INSERT, UPDATE ON public.synapsor_handler_receipts TO synapsor_writer;
68
79
  GRANT UPDATE (late_fee_cents, waiver_reason, credit_requested_cents, credit_reason, credited_cents, updated_at) ON public.invoices TO synapsor_writer;
69
80
  GRANT INSERT ON public.account_credits TO synapsor_writer;
@@ -168,6 +168,9 @@ class PostgresWritebackHandlerTransaction {
168
168
  this.receiptTable = receiptTable;
169
169
  }
170
170
  async ensureReceiptTable() {
171
+ const existing = await this.client.query("SELECT to_regclass($1) AS receipt_table", [this.receiptTableName()]);
172
+ if (existing.rows[0]?.receipt_table)
173
+ return;
171
174
  await this.client.query(`
172
175
  CREATE TABLE IF NOT EXISTS ${this.receiptTableName()} (
173
176
  idempotency_key text PRIMARY KEY,
@@ -4,8 +4,8 @@ demo:
4
4
  @printf '%s\n' "Unsafe shortcut:"
5
5
  @printf '%s\n' ' agent -> execute_sql("UPDATE invoices SET late_fee_cents = 0 ...")'
6
6
  @printf '%s\n' ""
7
- @printf '%s\n' "Synapsor quick proof:"
8
- npx -y @synapsor/runner demo --quick --no-interactive
7
+ @printf '%s\n' "Synapsor guarded-action proof:"
8
+ npx -y @synapsor/runner try --prove --yes --no-open
9
9
  @printf '%s\n' ""
10
10
  @printf '%s\n' "Risk audit example:"
11
11
  npx -y @synapsor/runner audit --example dangerous-db-mcp --format markdown
@@ -13,10 +13,12 @@ Expected output includes:
13
13
 
14
14
  ```text
15
15
  execute_sql
16
- Synapsor quick demo complete.
17
- proposal created
18
- source DB changed: no
19
- approval required outside MCP
16
+ late_fee_cents: 5500 -> 0
17
+ Source changed:
18
+ No
19
+ Guarded commit complete.
20
+ restart-safe retry: yes
21
+ stale apply refused: yes
20
22
  ```
21
23
 
22
24
  The raw-SQL shortcut is:
@@ -1,7 +1,11 @@
1
- .PHONY: demo unsafe clean
1
+ .PHONY: demo unsafe evaluate clean
2
2
 
3
3
  demo:
4
4
  cd ../.. && examples/support-billing-agent/scripts/run-demo.sh
5
+ cd ../.. && examples/support-billing-agent/scripts/run-evaluation.sh
6
+
7
+ evaluate:
8
+ cd ../.. && examples/support-billing-agent/scripts/run-evaluation.sh
5
9
 
6
10
  unsafe:
7
11
  @printf '%s\n' "Unsafe raw-SQL shortcut this demo avoids:"